Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature regions #21

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 50 additions & 5 deletions core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,56 @@
from __future__ import unicode_literals

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

# Register/Unregister models here.
from core.models import UserRepo, Issue, IssueLabel
from core.models import UserRepo, Issue, IssueLabel, Region, RegionAdmin
from django.contrib.auth.models import *

admin.site.unregister(Group)
admin.site.unregister(User)
admin.site.register(UserRepo)

class UserRepoAdmin(admin.ModelAdmin):
"""Used to alter `UserRepo` admin site."""
fieldsets = (
(None, {
'fields': ('user', 'repo', 'regions')
}),
)

def formfield_for_manytomany(self, db_field, request, **kwargs):
"""Only show regions related to logged in user when filling `userrepo` form"""
if db_field.name == "regions":
kwargs["queryset"] = Region.objects.filter(regionadmin=request.user)
return super(UserRepoAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)

def save_form(self, request, form, change):
"""Automatically fills author by extracting it from currunt login user."""
obj = super( UserRepoAdmin, self).save_form(request, form, change)
if not change:
obj.author = request.user
return obj

def get_queryset(self, request):
"""Only let the user view their own `UserRepos`."""
qs = super(UserRepoAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)

def save_model(self, request, obj, form, change):
"""Save Model"""
obj.author = request.user
super(UserRepoAdmin, self).save_model(request, obj, form, change)

def has_change_permission(self, request, obj=None):
"""Only give the user permissions to modify their own `UserRepos`."""
if not obj:
return True
return obj.author == request.user or request.user.is_superuser


# Re-register UserAdmin
admin.site.register(User, UserAdmin)
admin.site.register(RegionAdmin)

admin.site.register(UserRepo, UserRepoAdmin)
admin.site.register(Region)
admin.site.register(Issue)
46 changes: 37 additions & 9 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,44 @@

from datetime import timedelta
from django.db import models
from core.utils.services import request_github_issues

from django.contrib.auth.models import AbstractUser
from celery.decorators import periodic_task

from core.utils.services import request_github_issues

ISSUE_UPDATE_PERIOD = 15 # in minutes


class Region(models.Model):
"""Used to store data for different regions."""
region_name = models.CharField(max_length=100, unique=True)
region_image = models.URLField(blank=True)

class Meta:
ordering = ('region_name',) # Ascending order according to region name.

def __str__(self):
return '%s' % (self.region_name)


class RegionAdmin(AbstractUser):
regions = models.ManyToManyField(Region)


class UserRepo(models.Model):
"""
UserRepo model is used to store the username and repo-name
for a repository.
"""
user = models.CharField(max_length=100)
repo = models.CharField(max_length=100)
author = models.ForeignKey(RegionAdmin)
regions = models.ManyToManyField(Region)
created = models.DateTimeField(auto_now_add=True)

class Meta:
ordering = ('created',) # Ascending order according to date created.
unique_together = ("user", "repo") # Avoid repo duplicates.
unique_together = ("user", "repo", "author") # Avoid repo duplicates.

def __str__(self):
return '/%s/%s' % (self.user, self.repo)
Expand Down Expand Up @@ -69,7 +89,8 @@ class Issue(models.Model):
issue_labels = models.ManyToManyField(IssueLabel, blank=True)
issue_url = models.URLField()
issue_body = models.TextField()

regions = models.ManyToManyField(Region)

class Meta:
ordering = ('updated_at',) # Ascending order according to updated_at.

Expand All @@ -80,23 +101,29 @@ def periodic_issues_updater():
Update `Issue` model in the database in every
`ISSUE_UPDATE_PERIOD` minutes.
"""
list_of_repos = UserRepo.objects.values('user', 'repo',)
list_of_repos = UserRepo.objects.values('id', 'user', 'repo',)
for repo in list_of_repos:
region_queryset = retrive_regions_for_a_user(repo['id'])
issue_list = request_github_issues(repo['user'], repo['repo'])
if issue_list['error']:
print "Error" + str(issue_list['data'])
else:
for issue in issue_list['data']:
validate_and_store_issue(issue)
validate_and_store_issue(issue, region_queryset)

def retrive_regions_for_a_user(user_repo_id):
"""Fetches all the regions related to a user."""
region_queryset = Region.objects.filter(userrepo=user_repo_id)
return region_queryset

def validate_and_store_issue(issue):
def validate_and_store_issue(issue, region_queryset):
"""
Validate issue:- if valid - store it into database,
else - Do not store in database
"""
if is_issue_state_open(issue):
if is_issue_valid(issue):
store_issue_in_db(issue)
store_issue_in_db(issue, region_queryset)

def is_issue_state_open(issue):
"""
Expand All @@ -121,7 +148,7 @@ def is_issue_valid(issue):
print 'Issue with id ' + str(issue['id']) + ' is not valid for our system.'
return True # issue is valid

def store_issue_in_db(issue):
def store_issue_in_db(issue, region_queryset):
"""Stores issue in db"""
experience_needed, language, expected_time, technology_stack = parse_issue(issue['body'])
experience_needed = experience_needed.strip().lower()
Expand All @@ -140,6 +167,7 @@ def store_issue_in_db(issue):
label_url=label['url'], label_color=label['color'])
label_instance.save()
issue_instance.issue_labels.add(label_instance)
issue_instance.regions.add(*region_queryset)

def delete_closed_issues(issue):
"""Delete issues that are closed on GitHub but present in our db"""
Expand Down
13 changes: 11 additions & 2 deletions core/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from rest_framework import serializers
from core.models import UserRepo, Issue, IssueLabel
from core.models import UserRepo, Issue, IssueLabel, Region


class UserRepoSerializer(serializers.ModelSerializer):
Expand All @@ -20,6 +20,15 @@ class Meta:
fields = ('label_id', 'label_name', 'label_color', 'label_url',)


class RegionSerializer(serializers.ModelSerializer):
"""
Serializer for `Region` Model.
"""
class Meta:
model = Region
fields = ('id','region_name', 'region_image',)


class IssueSerializer(serializers.ModelSerializer):
"""
Serializer for `Issue` Model.
Expand All @@ -30,4 +39,4 @@ class Meta:
model = Issue
fields = ('issue_id', 'title', 'experience_needed', 'expected_time',
'language', 'tech_stack', 'created_at', 'updated_at',
'issue_number', 'issue_labels', 'issue_url', 'issue_body')
'issue_number', 'issue_labels', 'issue_url', 'issue_body', 'regions',)
72 changes: 66 additions & 6 deletions core/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from requests.exceptions import ConnectionError

from .models import (UserRepo, parse_issue, validate_and_store_issue, Issue, delete_closed_issues,
is_issue_valid, is_issue_state_open, periodic_issues_updater)
is_issue_valid, is_issue_state_open, periodic_issues_updater, Region, RegionAdmin,
retrive_regions_for_a_user)
from .utils.mock_api import api_response_issues
from .utils.services import request_github_issues

Expand Down Expand Up @@ -46,7 +47,10 @@ def setUp(self):
"""Define the test client and other test variables."""
self.user = 'razat249'
self.repo = 'github-view'
self.user_repo = UserRepo(user=self.user, repo=self.repo)
self.author = RegionAdmin.objects.create_user(
username='jacob', password='top_secret'
)
self.user_repo = UserRepo(user=self.user, repo=self.repo, author=self.author)

def test_user_repo_model_can_create_a_userrepo(self):
"""Test the `UserRepo` model can create a `user_repo`."""
Expand All @@ -63,13 +67,46 @@ def test_user_repo_model_can_delete_a_userrepo(self):
new_count = UserRepo.objects.count()
self.assertEqual(old_count, new_count)

class RegionModelTestCase(TestCase):
"""This class defines the test suite for the `Region` model."""

def setUp(self):
"""Define the test client and other test variables."""
self.region_name = 'Mozilla India'
self.region_image = 'https://example.com/image.jpg'
self.region_instance = Region(region_name=self.region_name, region_image=self.region_image)

def test_region_model_can_create_region(self):
"""Tests `Region` model can create Regions"""
old_count = Region.objects.count()
self.region_instance.save()
new_count = Region.objects.count()
self.assertNotEqual(old_count, new_count)

def test_region_model_can_delete_region(self):
"""Tests `Region` model can delete Regions"""
old_count = Region.objects.count()
self.region_instance.save()
self.region_instance.delete()
new_count = Region.objects.count()
self.assertEqual(old_count, new_count)

class IssueModelAndFetcherTestCase(TestCase):
"""This class defines the test suite for the `issue fetcher` component."""

def setUp(self):
"""Initial setup for running tests."""
pass
self.USER_ID = 1
self.USER_REPO_ID = 1
self.author = RegionAdmin.objects.create_user(
id=self.USER_ID, username='jacob', password='top_secret'
)
self.region = Region(region_name="Mozilla India")
self.region.save()
self.user_repo = UserRepo(id=self.USER_REPO_ID, user='razat249', repo='github-view', author=self.author)
self.user_repo.save()
self.user_repo.regions.add(self.region)
self.region_queryset = Region.objects.filter(userrepo=self.USER_ID)

def test_api_can_request_issues(self):
"""Test the request function"""
Expand Down Expand Up @@ -116,28 +153,51 @@ def test_issue_state_open_or_not(self):
def test_validate_and_store_issue(self):
"""Test for validating and storing issues."""
old_count = Issue.objects.count()
validate_and_store_issue(SAMPLE_VALID_ISSUE)
validate_and_store_issue(SAMPLE_VALID_ISSUE, self.region_queryset)
new_count = Issue.objects.count()
self.assertLess(old_count, new_count)

def test_api_can_delete_closed_issues_in_db(self):
"""Test for checking if issues are deleted when closed online but present in db"""
issue = SAMPLE_VALID_ISSUE.copy()
validate_and_store_issue(issue)
validate_and_store_issue(issue, self.region_queryset)
issue['state'] = 'closed'
old_count = Issue.objects.count()
delete_closed_issues(issue)
new_count = Issue.objects.count()
self.assertLess(new_count, old_count)

def test_retrive_regions_for_a_user(self):
"""Test function can retrive regions for a user."""
regions = retrive_regions_for_a_user(self.USER_REPO_ID)
self.assertEqual(regions[0], self.region_queryset[0])

class ViewTestCase(TestCase):
"""This class defines the test suite for the api views."""

def setUp(self):
"""Define the test client and other test variables."""
self.mock_regions = ["a", "n", "e", "b", "d", "c"]
self.client = APIClient()
region_queryset = Region.objects.all()
for issue in api_response_issues:
validate_and_store_issue(issue)
validate_and_store_issue(issue, region_queryset)

def test_api_can_get_region_list(self):
"""Test the api can get given region list."""
response = self.client.get('/regionlist/', format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_api_can_get_region_list_ordered_by_name(self):
"""Test the api gives list of regions in accessending order."""
for s in self.mock_regions:
region_list = Region(region_name=s)
region_list.save()
response = self.client.get('/regionlist/', format="json")
response_content = json.loads(response.content)
sorted_mock_regions = sorted(self.mock_regions)
for i in xrange(len(sorted_mock_regions)):
self.assertEqual(sorted_mock_regions[i], response_content[i]['region_name'])

def test_api_can_get_metadata(self):
"""Test the api can get given metadata."""
Expand Down
16 changes: 12 additions & 4 deletions core/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from core.models import UserRepo, Issue
from core.serializers import UserRepoSerializer, IssueSerializer
from core.models import UserRepo, Issue, Region
from core.serializers import UserRepoSerializer, IssueSerializer, RegionSerializer
from rest_framework import generics
from django_filters.rest_framework import DjangoFilterBackend
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
from rest_framework.views import APIView
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
Expand All @@ -18,6 +18,14 @@ class UserRepoList(generics.ListAPIView):
filter_fields = ('repo', 'user',)


class RegionList(generics.ListAPIView):
"""
Returns a list of regions.
"""
queryset = Region.objects.all()
serializer_class = RegionSerializer


class IssueList(generics.ListAPIView):
"""
Returns a list of issues, by optionally filtering against
Expand All @@ -27,7 +35,7 @@ class IssueList(generics.ListAPIView):
queryset = Issue.objects.all()
serializer_class = IssueSerializer
filter_backends = (DjangoFilterBackend, OrderingFilter,)
filter_fields = ('language', 'tech_stack', 'experience_needed', 'expected_time',)
filter_fields = ('language', 'tech_stack', 'experience_needed', 'expected_time', 'regions',)
ordering_fields = ('experience_needed', 'expected_time')


Expand Down
2 changes: 2 additions & 0 deletions issue_parser/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
'django_nose',
]

AUTH_USER_MODEL = 'core.RegionAdmin'

# Use nose to run all tests
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

Expand Down
1 change: 1 addition & 0 deletions issue_parser/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from django.contrib import admin

urlpatterns = [
url(r'^regionlist/$', views.RegionList.as_view()),
url(r'^userrepos/$', views.UserRepoList.as_view()),
url(r'^issues/$', views.IssueList.as_view()),
url(r'^metadata/$', views.MetaData.as_view()),
Expand Down