From 6bc8bedc3beeafebdd23c759856ba99584736984 Mon Sep 17 00:00:00 2001 From: Leonhard Kuboschek Date: Fri, 10 Mar 2017 13:43:38 +0100 Subject: [PATCH] Switch authentication to Dreamjub Previously, authentication worked by transmitting the password to the legacy OpenJUB API. Furthermore user data was also received from this API. This caused problem, as Jay was able potentially able to intercept user passwords and also depended on a legacy system. This commit updates the authentication to use the new dreamjub api via OAuth. Furthermore, it updates the permission system to move away from UserProfiles and SuperAdmin models. Instead, this commit makes use of internal Django mechanisms and stores user data directly with the model. See also issue #26. --- core/views.py | 11 +- filters/models.py | 96 +++--- filters/views.py | 16 +- jay/settings.py | 17 +- jay/urls.py | 2 +- jay/utils.py | 78 ++++- requirements.txt | 8 +- settings/models.py | 41 +-- settings/urls.py | 8 +- templates/base/base.html | 6 +- templates/vote/fragments/vote_list.html | 4 +- users/models.py | 107 ++---- users/ojub_auth.py | 70 ---- votes/models.py | 427 ++++++++++++------------ votes/views.py | 10 +- 15 files changed, 433 insertions(+), 468 deletions(-) diff --git a/core/views.py b/core/views.py index ebfcf6a..83625dc 100644 --- a/core/views.py +++ b/core/views.py @@ -7,6 +7,7 @@ from filters.models import UserFilter from users.models import UserProfile + # Create your views here. def home(request): ctx = {} @@ -15,8 +16,14 @@ def home(request): systems = VotingSystem.objects.all() if request.user.is_authenticated(): - details = json.loads(request.user.profile.details) - votes_shown = [v for v in votes if v.filter.matches(details)] + try: + details = request.user.socialaccount_set.get( + provider='dreamjub').extra_data + except request.user.DoesNotExist: + details = {} + + votes_shown = [v for v in votes if v.filter.matches(json.loads( + details))] ctx["vote_list_title"] = "Your votes" diff --git a/filters/models.py b/filters/models.py index a5931a9..5cd9ed1 100644 --- a/filters/models.py +++ b/filters/models.py @@ -9,56 +9,56 @@ from settings.models import VotingSystem import filters.forest as forest +from jay import utils # Create your models here. class UserFilter(models.Model): - system = models.ForeignKey(VotingSystem) - name = models.CharField(max_length=255) - value = models.CharField(max_length=255) - tree = models.TextField(blank=True) - - def __str__(self): - return u'%s: %s' % (self.system.machine_name, self.name) - - def clean(self): - try: - self.tree = json.dumps(forest.parse_and_simplify(self.value)) - except Exception as e: - self.tree = None - - if self.tree == None: - raise ValidationError({ - 'value': ValidationError('Value for \'value\' invalid: Can not parse into a valid logical tree. ', code='invalid') - }) - - def matches(self, obj): - """ - Checks if this filter matches an object. - """ - - try: - return forest.matches(json.loads(self.tree), obj) - except Exception as e: - import sys - sys.stderr.write(e) - return False - - def map_matches(self, objs): - - try: - return forest.map_match(json.loads(self.tree), objs) - except Exception as e: - return False - - def canEdit(self, user): - """ - Checks if a user can edit this UserFilter. - """ - - return self.system.isAdmin(user) - - def get_absolute_url(self): - from django.core.urlresolvers import reverse - return reverse('filters:edit', kwargs={'filter_id':self.id}) + system = models.ForeignKey(VotingSystem) + name = models.CharField(max_length=255) + value = models.CharField(max_length=255) + tree = models.TextField(blank=True) + + def __str__(self): + return u'%s: %s' % (self.system.machine_name, self.name) + + def clean(self): + try: + self.tree = json.dumps(forest.parse_and_simplify(self.value)) + except Exception as e: + self.tree = None + + if self.tree == None: + raise ValidationError({ + 'value': ValidationError('Value for \'value\' invalid: Can not parse into a valid logical tree. ', code='invalid') + }) + + def matches(self, obj): + """ + Checks if this filter matches an object. + """ + + try: + return forest.matches(json.loads(self.tree), obj) + except Exception as e: + import sys + sys.stderr.write(e) + return False + + def map_matches(self, objs): + + try: + return forest.map_match(json.loads(self.tree), objs) + except Exception as e: + return False + + def canEdit(self, user): + """ + Checks if a user can edit this UserFilter. + """ + return utils.is_admin_for(user, self.system) + + def get_absolute_url(self): + from django.core.urlresolvers import reverse + return reverse('filters:edit', kwargs={'filter_id':self.id}) admin.site.register(UserFilter) diff --git a/filters/views.py b/filters/views.py index 9671185..ce22070 100644 --- a/filters/views.py +++ b/filters/views.py @@ -12,12 +12,12 @@ from filters.forms import NewFilterForm, EditFilterForm, FilterTestForm, FilterTestUserForm import filters.forest as forest -import json - from votes.models import VotingSystem -from jay.utils import priviliged +from jay.utils import priviliged, is_elevated, get_all_systems, is_admin_for, get_user_details + +import json FILTER_FOREST_TEMPLATE = "filters/filter_forest.html" FILTER_EDIT_TEMPLATE = "filters/filter_edit.html" @@ -28,7 +28,7 @@ def Forest(request, alert_type=None, alert_head=None, alert_text=None): # if the user does not have enough priviliges, throw an exception - if not request.user.profile.isElevated(): + if not is_elevated(request.user): raise PermissionDenied # build a new context @@ -40,7 +40,7 @@ def Forest(request, alert_type=None, alert_head=None, alert_text=None): bc.append({'url':reverse('filters:forest'), 'text':'Filters', 'active':True}) ctx['breadcrumbs'] = bc - (admin_systems, other_systems) = request.user.profile.getSystems() + (admin_systems, other_systems) = get_all_systems(request.user) # give those to the view ctx['admin_systems'] = admin_systems @@ -77,7 +77,7 @@ def FilterNew(request): # check if the user can edit it. # if not, go back to the overview - if not system.isAdmin(request.user.profile): + if not is_admin_for(request.user, system): return Forest(request, alert_head="Creation failed", alert_text="Nice try. You are not allowed to edit this VotingSystem. ") # create a new filter @@ -136,7 +136,7 @@ def FilterEdit(request, filter_id): ctx["filter"] = filter # check if the user can edit it - if not filter.canEdit(request.user.profile): + if not filter.canEdit(request.user): raise PermissionDenied # Set up the breadcrumbs @@ -244,7 +244,7 @@ def FilterTestUser(request, filter_id): form = FilterTestUserForm(request.POST) if form.is_valid(): obj = form.cleaned_data["user"] - obj = User.objects.filter(username=obj)[0].profile.details + obj = json.dumps(get_user_details(User.objects.filter(username=obj)[0])) except Exception as e: print(e) pass diff --git a/jay/settings.py b/jay/settings.py index 60aacbe..aa4bb87 100644 --- a/jay/settings.py +++ b/jay/settings.py @@ -26,7 +26,15 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.sites', + 'django_forms_bootstrap', + + 'allauth', + 'allauth.account', + 'allauth.socialaccount', + 'dreamjub.providers.oauth', + 'filters', 'settings', 'users', @@ -66,8 +74,10 @@ WSGI_APPLICATION = 'jay.wsgi.application' # OpenJUB auth -AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', - 'users.ojub_auth.OjubBackend') +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', + 'allauth.account.auth_backends.AuthenticationBackend', +) # Default after login redirect # These are named URL routes @@ -93,3 +103,6 @@ ) STATIC_URL = '/static/' +SITE_ID = 1 + +ACCOUNT_EMAIL_VERIFICATION = "none" \ No newline at end of file diff --git a/jay/urls.py b/jay/urls.py index 37c9f9a..058ecd2 100644 --- a/jay/urls.py +++ b/jay/urls.py @@ -42,7 +42,7 @@ url(r'^help/filters/$', TemplateView.as_view(template_name="filters/filter_help.html"), name="filter_help"), # Authentication - url(r'^login/', auth_views.login, {'template_name': 'auth/login.html'}, name="login"), + url(r'^accounts/', include('allauth.urls'), name='login'), url(r'^logout/', auth_views.logout, {'template_name': 'auth/logout.html', 'next_page':'home'}, name="logout"), # Sub-projects diff --git a/jay/utils.py b/jay/utils.py index 68c59d0..59c3d81 100644 --- a/jay/utils.py +++ b/jay/utils.py @@ -1,34 +1,94 @@ from django.core.exceptions import PermissionDenied + def memoize(f): memo = {} + def helper(*x): key = str(x) if key not in memo: memo[key] = f(*x) return memo[key] + return helper + def superadmin(handler): """ - Checks if a user is a super admin. + Checks if a user is a super admin. """ - + def helper(request, *args, **kwargs): - if not request.user.profile.isSuperAdmin(): + if not request.user.is_superuser: raise PermissionDenied return handler(request, *args, **kwargs) - + return helper + +def is_elevated(user): + if not user.is_superuser: + if not user.admin_set.count() > 0: + return False + return True + + def priviliged(handler): """ - Checks that a user has elevated priviliges. + Checks that a user has elevated priviliges. """ + def helper(request, *args, **kwargs): - if not request.user.profile.isElevated(): + if not is_elevated(request.user): raise PermissionDenied - + return handler(request, *args, **kwargs) - - return helper \ No newline at end of file + + return helper + + +def get_user_details(user): + import json + + try: + data = user.socialaccount_set.get(provider="dreamjub").extra_data + return data + except: + return {} + + +def is_admin_for(user, system): + """ + Checks if this user can administer a certain voting system. + """ + return system in get_administrated_systems(user) + + +def get_administrated_systems(user): + from settings.models import VotingSystem, Admin + """ + Returns all voting systems this user can administer. + """ + # if we are a superadmin we can manage all systems + if user.is_superuser: + return VotingSystem.objects.all() + + # else return only the systems we are an admin for. + else: + return list(map(lambda x: x.system, Admin.objects.filter( + user=user))) + + +def get_all_systems(user): + from settings.models import VotingSystem + """ + Gets the editable filters for this user. + """ + + # get all the voting systems for this user + admin_systems = get_administrated_systems(user) + + # and all the other ones also + other_systems = list(filter(lambda a: not a in admin_systems, VotingSystem.objects.all())) + + return admin_systems, other_systems diff --git a/requirements.txt b/requirements.txt index 2ea77cd..55ff99b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,5 +10,9 @@ Markdown==2.6.5 # For filters PyExecJS==1.1.0 -# For OpenJUB auth backend -requests==2.6.0 +# For reaching out to OpenJUB +requests==2.12.3 + +# For authing against dreamjub +django-allauth==0.29.0 +django-allauth-dreamjub==0.1.3 diff --git a/settings/models.py b/settings/models.py index 473565b..c17947c 100644 --- a/settings/models.py +++ b/settings/models.py @@ -3,28 +3,29 @@ from django.contrib import admin from jay.restricted import is_restricted_word +from users.models import Admin class VotingSystem(models.Model): - machine_name = models.SlugField(max_length = 50, unique = True) - simple_name = models.CharField(max_length = 80) - - def __str__(self): - return u'[%s] %s' % (self.machine_name, self.simple_name) - - def clean(self): - is_restricted_word('machine_name', self.machine_name) - - def canEdit(self, user): - """ - Checks if a user can edit this voting system. - """ - return user.isSuperAdmin() - - def isAdmin(self, user): - """ - Checks if a user is an administrator for this voting system. - """ - return user.isAdminFor(self) + machine_name = models.SlugField(max_length = 50, unique = True) + simple_name = models.CharField(max_length = 80) + + def __str__(self): + return u'[%s] %s' % (self.machine_name, self.simple_name) + + def clean(self): + is_restricted_word('machine_name', self.machine_name) + + def canEdit(self, user): + """ + Checks if a user can edit this voting system. + """ + return user.isSuperAdmin() + + def isAdmin(self, user): + """ + Checks if a user is an administrator for this voting system. + """ + return Admin.objects.filter(system=self, user=user).exists() admin.site.register(VotingSystem) diff --git a/settings/urls.py b/settings/urls.py index 8259cc4..c6ced47 100644 --- a/settings/urls.py +++ b/settings/urls.py @@ -1,14 +1,14 @@ from django.conf.urls import url from django.views.generic import TemplateView -from settings.views import superadmins, systems +from settings.views import systems urlpatterns = [ # Superadmin management - url(r'^$', superadmins.settings, name="settings"), - url(r'^superadmins/add$', superadmins.superadmin_add, name="add"), - url(r'^superadmins/(?P[\w-]+)/remove$', superadmins.superadmin_remove, name="remove"), + #url(r'^$', superadmins.settings, name="settings"), + #url(r'^superadmins/add$', superadmins.superadmin_add, name="add"), + #url(r'^superadmins/(?P[\w-]+)/remove$', superadmins.superadmin_remove, name="remove"), # System management url(r'^systems$', systems.systems, name='systems'), diff --git a/templates/base/base.html b/templates/base/base.html index dfa528a..7b039a0 100644 --- a/templates/base/base.html +++ b/templates/base/base.html @@ -43,11 +43,11 @@ {{ user.username }} {% else %} - Login + Login {% endif %} diff --git a/templates/vote/fragments/vote_list.html b/templates/vote/fragments/vote_list.html index f73dc3b..6c7cf1f 100644 --- a/templates/vote/fragments/vote_list.html +++ b/templates/vote/fragments/vote_list.html @@ -12,13 +12,13 @@  {{ vote.name }} - {% if vote|can_delete:request.user.profile %} + {% if vote|can_delete:request.user %} {% endif %} - {% if vote|can_edit:request.user.profile %} + {% if vote|can_edit:request.user %} diff --git a/users/models.py b/users/models.py index 2497790..1a989aa 100644 --- a/users/models.py +++ b/users/models.py @@ -5,95 +5,42 @@ from django.contrib import admin -from settings.models import VotingSystem - import json + # Create your models here. class Admin(models.Model): - user = models.ForeignKey(User) - system = models.ForeignKey(VotingSystem) - - class Meta(): - unique_together = (("system", "user")) - - def __str__(self): - return u'[%s] %s' % (self.system.machine_name, self.user) + user = models.ForeignKey(User) + system = models.ForeignKey("settings.VotingSystem") + class Meta(): + unique_together = (("system", "user")) + def __str__(self): + return u'[%s] %s' % (self.system.machine_name, self.user) -class SuperAdmin(models.Model): - user = models.ForeignKey(User) - - class Meta(): - unique_together = (("user",),) - - def __str__(self): - return u'%s' % (self.user) class UserProfile(models.Model): - user = models.OneToOneField(User, related_name="profile") - details = models.TextField() - - def __str__(self): - return u'[Profile] %s' % (self.user.username) - - def clean(self): - # make sure that the details are a valid json object - try: - json.loads(self.details) - except: - raise ValidationError({ - 'details': ValidationError('Details needs to be a valid JSON object', code='invalid') - }) - def isSuperAdmin(self): - """ - Returns if this user is a SuperAdmin. - """ - return self.user.superadmin_set.count() > 0 - def isAdminFor(self, system): - """ - Checks if this user can administer a certain voting system. - """ - return system in self.getAdministratedSystems() - def getAdministratedSystems(self): - """ - Returns all voting systems this user can administer. - """ - # if we are a superadmin we can manage all systems - if self.isSuperAdmin(): - return VotingSystem.objects.all() - - # else return only the systems we are an admin for. - else: - return list(map(lambda x: x.system, Admin.objects.filter(user=self.user))) - def isElevated(self): - """ - Checks if this user is an elevated user. - - (i. e. if they are a superadmin or admin for some voting system) - """ - - # thy are a superadmin - if self.isSuperAdmin(): - return True - - # they administer some voting system - return self.user.admin_set.count() > 0 - - def getSystems(self): - """ - Gets the editable filters for this user. - """ - - # get all the voting systems for this user - admin_systems = self.getAdministratedSystems() - - # and all the other ones also - other_systems = list(filter(lambda a: not a in admin_systems, VotingSystem.objects.all())) - - return (admin_systems, other_systems) + user = models.OneToOneField(User, related_name="profile") + details = models.TextField() + + def __str__(self): + return u'[Profile] %s' % (self.user.username) + + def clean(self): + # make sure that the details are a valid json object + try: + json.loads(self.details) + except: + raise ValidationError({ + 'details': ValidationError('Details needs to be a valid JSON object', code='invalid') + }) + + def isSuperAdmin(self): + """ + Returns if this user is a SuperAdmin. + """ + return self.user.is_superuser admin.site.register(Admin) -admin.site.register(SuperAdmin) admin.site.register(UserProfile) diff --git a/users/ojub_auth.py b/users/ojub_auth.py index 15fcf61..9dd21bd 100644 --- a/users/ojub_auth.py +++ b/users/ojub_auth.py @@ -1,77 +1,7 @@ -from django.conf import settings -from django.contrib.auth.models import User - -from users.models import UserProfile - import requests OPENJUB_BASE = "https://api.jacobs.university/" -class OjubBackend(object): - """ - Authenticates credentials against the OpenJUB database. - - The URL for the server is configured by OPENJUB_BASE in the settings. - - This class does not fill in user profiles, this has to be handled - in other places - """ - def authenticate(self, username=None, password=None): - r = requests.post(OPENJUB_BASE + "auth/signin", - data = {'username':username, 'password': password}) - - if r.status_code != requests.codes.ok: - return None - - resp = r.json() - - uname = resp['user'] - token = resp['token'] - - details = requests.get(OPENJUB_BASE + "user/me", - params = {'token':token}) - - if details.status_code != requests.codes.ok: - print("Could not get user details") - return None - - try: - user = User.objects.get(username=uname) - except User.DoesNotExist: - user = User(username=uname) - - user.set_unusable_password() - - # TODO Don't hardcode this - if user.username in ["lkuboschek", "twiesing", "jinzhang", "rdeliallis"]: - user.is_staff = True - user.is_superuser = True - - data = details.json() - - user.first_name = data['firstName'] - user.last_name = data['lastName'] - user.email = data['email'] - - user.save() - - # Make a user profile if there isn't one already - try: - profile = UserProfile.objects.get(user=user) - except UserProfile.DoesNotExist: - profile = UserProfile(user=user) - - profile.details = details.text - profile.save() - - return user - - def get_user(self, user_id): - try: - return User.objects.get(pk=user_id) - except User.DoesNotExist: - return None - def get_all(username, password): r = requests.post(OPENJUB_BASE + "auth/signin", data = {'username':username, 'password': password}) diff --git a/votes/models.py b/votes/models.py index d507534..06fcb95 100644 --- a/votes/models.py +++ b/votes/models.py @@ -13,313 +13,314 @@ from users.ojub_auth import get_all from jay.restricted import is_restricted_word +from jay import utils # Create your models here. class Vote(models.Model): - system = models.ForeignKey(VotingSystem) + system = models.ForeignKey(VotingSystem) - name = models.CharField(max_length = 64) - machine_name = models.SlugField(max_length = 64) + name = models.CharField(max_length = 64) + machine_name = models.SlugField(max_length = 64) - auto_open_options = models.BooleanField(default = False) + auto_open_options = models.BooleanField(default = False) - filter = models.ForeignKey(UserFilter, null = True) - status = models.OneToOneField('Status') + filter = models.ForeignKey(UserFilter, null = True) + status = models.OneToOneField('Status') - description = models.TextField(blank = True) + description = models.TextField(blank = True) - creator = models.ForeignKey(User) + creator = models.ForeignKey(User) - min_votes = models.IntegerField() - max_votes = models.IntegerField() + min_votes = models.IntegerField() + max_votes = models.IntegerField() - class Meta(): - unique_together = (("system", "machine_name")) + class Meta(): + unique_together = (("system", "machine_name")) - def __str__(self): - return u'[%s] %s' % (self.machine_name, self.name) + def __str__(self): + return u'[%s] %s' % (self.machine_name, self.name) - def clean(self): - is_restricted_word('machine_name', self.machine_name) + def clean(self): + is_restricted_word('machine_name', self.machine_name) - def canEdit(self, user): - """ - Checks if a user can edit this vote. - """ - return user.isAdminFor(self.system) and self.status.stage != Status.PUBLIC + def canEdit(self, user): + """ + Checks if a user can edit this vote. + """ + return utils.is_admin_for(user, self.system) and self.status.stage != Status.PUBLIC - def canDelete(self, user): - """ - Check if a user can delete this vote. - """ - return user.isAdminFor(self.system) and self.status.stage == Status.INIT + def canDelete(self, user): + """ + Check if a user can delete this vote. + """ + return utils.is_admin_for(user, self.system) and self.status.stage == Status.INIT - def canBeModified(self): - """ - Checks if this vote can still be modified. - """ - return self.status.stage == Status.INIT + def canBeModified(self): + """ + Checks if this vote can still be modified. + """ + return self.status.stage == Status.INIT - def update_eligibility(self, username, password): + def update_eligibility(self, username, password): - PassiveVote.objects.get_or_create(vote=self, defaults={'num_voters': 0, 'num_eligible': 0}) + PassiveVote.objects.get_or_create(vote=self, defaults={'num_voters': 0, 'num_eligible': 0}) - if self.filter == None: - raise Exception("Missing filter. ") + if self.filter == None: + raise Exception("Missing filter. ") - # this will take really long - everyone = get_all(username, password) + # this will take really long + everyone = get_all(username, password) - if not everyone: - raise Exception("Invalid password or something went wrong. ") + if not everyone: + raise Exception("Invalid password or something went wrong. ") - check = self.filter.map_matches(everyone) - c = 0 + check = self.filter.map_matches(everyone) + c = 0 - for b in check: - if b: - c += 1 + for b in check: + if b: + c += 1 - # get or create the passive vote object - (pv, _) = PassiveVote.objects.get_or_create(vote=self, defaults={'num_voters': 0, 'num_eligible': 0}) + # get or create the passive vote object + (pv, _) = PassiveVote.objects.get_or_create(vote=self, defaults={'num_voters': 0, 'num_eligible': 0}) - # update the eligibility number - pv.num_eligible = c + # update the eligibility number + pv.num_eligible = c - # and save - pv.save() + # and save + pv.save() - # Touch yourself, we lack self-assurance - def touch(self): - status = self.status - stage = self.status.stage - now = timezone.now() + # Touch yourself, we lack self-assurance + def touch(self): + status = self.status + stage = self.status.stage + now = timezone.now() - if stage == Status.INIT: - return + if stage == Status.INIT: + return - if stage == Status.STAGED and status.open_time: - if status.open_time <= now: - self.status.stage = Status.OPEN + if stage == Status.STAGED and status.open_time: + if status.open_time <= now: + self.status.stage = Status.OPEN - if stage == Status.OPEN and status.close_time: - if status.close_time <= now: - self.status.stage = Status.CLOSE - self.close() + if stage == Status.OPEN and status.close_time: + if status.close_time <= now: + self.status.stage = Status.CLOSE + self.close() - if stage == Status.CLOSE and status.public_time: - if status.public_time <= now: - self.status.stage = Status.PUBLIC + if stage == Status.CLOSE and status.public_time: + if status.public_time <= now: + self.status.stage = Status.PUBLIC - self.status.save() + self.status.save() - """ + """ - Converts the ActiveVotes on this vote to a count on the PassiveVote of this Vote + Converts the ActiveVotes on this vote to a count on the PassiveVote of this Vote - """ - def close(self): - try: - pasv = self.passivevote - voters = self.activevote_set + """ + def close(self): + try: + pasv = self.passivevote + voters = self.activevote_set - pasv.num_voters = voters.count() - pasv.save() + pasv.num_voters = voters.count() + pasv.save() - voters.delete() + voters.delete() - except PassiveVote.DoesNotExist: - pass + except PassiveVote.DoesNotExist: + pass - @transaction.atomic - def renumberOptions(self): - """ - Renumbers the options in this vote. - """ + @transaction.atomic + def renumberOptions(self): + """ + Renumbers the options in this vote. + """ - # give all of them a sequential number. - for i, v in enumerate(self.option_set.order_by("number")): - v.number = i - v.save() + # give all of them a sequential number. + for i, v in enumerate(self.option_set.order_by("number")): + v.number = i + v.save() - @transaction.atomic - def deleteOption(self, option): - """ - Removes an option from this vote. - """ + @transaction.atomic + def deleteOption(self, option): + """ + Removes an option from this vote. + """ - # if the option is not in our options, something weird happened. - if not option.id in self.option_set.values_list('id', flat=True): - raise ValueError + # if the option is not in our options, something weird happened. + if not option.id in self.option_set.values_list('id', flat=True): + raise ValueError - # remove the option - option.delete() + # remove the option + option.delete() - # get the count - count = self.option_set.count() + # get the count + count = self.option_set.count() - # adn check if min_votes or max_votes are too big. - # then update accordingly. - if self.min_votes > count: - self.min_votes = count + # adn check if min_votes or max_votes are too big. + # then update accordingly. + if self.min_votes > count: + self.min_votes = count - if self.max_votes > count: - self.max_votes = count + if self.max_votes > count: + self.max_votes = count - # and save of course - self.save() + # and save of course + self.save() - # and renumber - self.renumberOptions() + # and renumber + self.renumberOptions() - @transaction.atomic - def addOption(self): - """ - Adds a new option. - """ + @transaction.atomic + def addOption(self): + """ + Adds a new option. + """ - # renumber the options to make sure we are in order - self.renumberOptions() + # renumber the options to make sure we are in order + self.renumberOptions() - # find a number for the new option - num = self.option_set.count() + # find a number for the new option + num = self.option_set.count() - # create a new option - opt = Option(vote=self, number = num, name = "Option #"+str(num + 1)) + # create a new option + opt = Option(vote=self, number = num, name = "Option #"+str(num + 1)) - # and save it - opt.save() + # and save it + opt.save() - @transaction.atomic - def moveDownOption(self, option): - """ - move an option down in the indexing. - """ + @transaction.atomic + def moveDownOption(self, option): + """ + move an option down in the indexing. + """ - # if the option is not in our options, something weird happened. - if not option.id in self.option_set.values_list('id', flat=True): - raise ValueError + # if the option is not in our options, something weird happened. + if not option.id in self.option_set.values_list('id', flat=True): + raise ValueError - # renumber options to make sure we are in a valid order - self.renumberOptions() + # renumber options to make sure we are in a valid order + self.renumberOptions() - # if we are already at the bottom there is nothing to do. - if option.number == 0: - return + # if we are already at the bottom there is nothing to do. + if option.number == 0: + return - # find the option right below our option - below = self.option_set.filter(number=option.number - 1)[0] + # find the option right below our option + below = self.option_set.filter(number=option.number - 1)[0] - # switch our two number - below.number = option.number - option.number = option.number - 1 + # switch our two number + below.number = option.number + option.number = option.number - 1 - # and save the options - below.save() - option.save() + # and save the options + below.save() + option.save() - @transaction.atomic - def moveUpOption(self, option): - """ - move an option up in the indexing. - """ + @transaction.atomic + def moveUpOption(self, option): + """ + move an option up in the indexing. + """ - # if the option is not in our options, something weird happened. - if not option.id in self.option_set.values_list('id', flat=True): - raise ValueError + # if the option is not in our options, something weird happened. + if not option.id in self.option_set.values_list('id', flat=True): + raise ValueError - # renumber options to make sure we are in a valid order - self.renumberOptions() + # renumber options to make sure we are in a valid order + self.renumberOptions() - # if we are already at the top there is nothing to do. - if option.number == self.option_set.count() - 1: - return + # if we are already at the top there is nothing to do. + if option.number == self.option_set.count() - 1: + return - # find the option right above our option - above = self.option_set.filter(number=option.number + 1)[0] + # find the option right above our option + above = self.option_set.filter(number=option.number + 1)[0] - # switch our two number - above.number = option.number - option.number = option.number + 1 + # switch our two number + above.number = option.number + option.number = option.number + 1 - # and save the options - above.save() - option.save() + # and save the options + above.save() + option.save() class Option(models.Model): - vote = models.ForeignKey(Vote) + vote = models.ForeignKey(Vote) - number = models.IntegerField() + number = models.IntegerField() - name = models.CharField(max_length = 64) - description = models.TextField(blank = True) + name = models.CharField(max_length = 64) + description = models.TextField(blank = True) - picture_url = models.URLField(blank = True) + picture_url = models.URLField(blank = True) - personal_link = models.URLField(blank = True) - link_name = models.CharField(blank = True, max_length = 16) + personal_link = models.URLField(blank = True) + link_name = models.CharField(blank = True, max_length = 16) - count = models.IntegerField(default = 0, blank = True) + count = models.IntegerField(default = 0, blank = True) - class Meta(): - unique_together = (("vote", "number")) + class Meta(): + unique_together = (("vote", "number")) - def __str__(self): - return u'[%s] %s' % (self.number, self.name) + def __str__(self): + return u'[%s] %s' % (self.number, self.name) - def canEdit(self, user): - """ - Checks if a user can edit this option. - """ - return self.vote.canEdit(user) + def canEdit(self, user): + """ + Checks if a user can edit this option. + """ + return self.vote.canEdit(user) class Status(models.Model): - INIT = 'I' - STAGED = 'S' - OPEN = 'O' - CLOSE = 'C' - PUBLIC = 'P' - - STAGES = ( - (INIT, 'Init'), - (STAGED, 'Staged'), - (OPEN, 'Open'), - (CLOSE, 'Close'), - (PUBLIC, 'Results public') - ) - - open_time = models.DateTimeField(blank = True, null = True) - close_time = models.DateTimeField(blank = True, null = True) - public_time = models.DateTimeField(blank = True, null = True) - stage = models.CharField(max_length = 1, choices = STAGES, default = INIT) - - def __str__(self): - return self.stage + INIT = 'I' + STAGED = 'S' + OPEN = 'O' + CLOSE = 'C' + PUBLIC = 'P' + + STAGES = ( + (INIT, 'Init'), + (STAGED, 'Staged'), + (OPEN, 'Open'), + (CLOSE, 'Close'), + (PUBLIC, 'Results public') + ) + + open_time = models.DateTimeField(blank = True, null = True) + close_time = models.DateTimeField(blank = True, null = True) + public_time = models.DateTimeField(blank = True, null = True) + stage = models.CharField(max_length = 1, choices = STAGES, default = INIT) + + def __str__(self): + return self.stage class ActiveVote(models.Model): - vote = models.ForeignKey(Vote) + vote = models.ForeignKey(Vote) - user = models.ForeignKey(User) + user = models.ForeignKey(User) - def __str__(self): - return u'%s voted for %s' % (self.user, self.vote) + def __str__(self): + return u'%s voted for %s' % (self.user, self.vote) class PassiveVote(models.Model): - vote = models.OneToOneField(Vote) + vote = models.OneToOneField(Vote) - num_voters = models.IntegerField() - num_eligible = models.IntegerField() + num_voters = models.IntegerField() + num_eligible = models.IntegerField() - def __str__(self): - return u'%s of %s voted' % (self.num_voters, self.num_eligible) + def __str__(self): + return u'%s of %s voted' % (self.num_voters, self.num_eligible) admin.site.register(Vote) admin.site.register(Option) diff --git a/votes/views.py b/votes/views.py index 695febe..b4dd289 100644 --- a/votes/views.py +++ b/votes/views.py @@ -49,7 +49,7 @@ def system_home(request, system_name): all_votes = Vote.objects.filter(system=vs) - if request.user.is_authenticated() and vs.isAdmin(request.user.profile): + if request.user.is_authenticated() and vs.isAdmin(request.user): ctx['votes'] = all_votes ctx['results'] = Vote.objects.filter(system=vs, status__stage__in=[Status.PUBLIC, Status.CLOSE]) @@ -203,7 +203,9 @@ def get_vote_props(ctx, vote): return ctx + def vote_edit_context(request, system_name, vote_name): + from jay import utils """ Returns context and basic parameters for vote editing. """ @@ -213,14 +215,14 @@ def vote_edit_context(request, system_name, vote_name): vote.touch() # raise an error if the user trying to access is not an admin - if not system.isAdmin(request.user.profile): + if not system.isAdmin(request.user): raise PermissionDenied # make a context ctx = {} # get all the systems this user can edit - (admin_systems, other_systems) = request.user.profile.getSystems() + (admin_systems, other_systems) = utils.get_all_systems(request.user) # add the vote to the system ctx['vote'] = vote @@ -256,7 +258,7 @@ def vote_add(request, system_name): vs = get_object_or_404(VotingSystem, machine_name=system_name) # raise an error if the user trying to access is not an admin - if not vs.isAdmin(request.user.profile): + if not vs.isAdmin(request.user): raise PermissionDenied v = Vote()