Skip to content

Commit

Permalink
Society to societies data migration and fields
Browse files Browse the repository at this point in the history
Relatively big change. We remove the society OneToOneField, and replace
it with a ManyToManyField supported by a Employment "via" class.

This means that in addition to querying costing a bit more, although
c'mon, we're not big, we have to create employment relations between
workers to facilitate who they work for.

On the plus side we can now have workers work for many different
societies, which is really convenienent for the few that do, and for us
wanting a single unified employment relationship.
After all, there's no reason a different individual should in any way be
duplicated just because of an oversight on our part in terms of use
cases.
  • Loading branch information
thor authored and maxiix3 committed Oct 26, 2017
1 parent 24af8b7 commit c674575
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 27 deletions.
2 changes: 1 addition & 1 deletion spbm/apps/society/admin/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class ShiftInline(ReadOnlyProtection, admin.TabularInline):
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == "worker":
if not request.user.is_superuser:
kwargs['queryset'] = Worker.objects.filter(society=request.user.spfuser.society)
kwargs['queryset'] = Worker.objects.filter(societies=request.user.spfuser.society)
return super(ShiftInline, self).formfield_for_choice_field(db_field, request, **kwargs)

def get_readonly_fields(self, request, obj=None):
Expand Down
4 changes: 2 additions & 2 deletions spbm/apps/society/admin/worker.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.contrib import admin

from .events import ShiftInline
from ..forms.worker import WorkerForm
from ..forms.workers import WorkerForm
from ..models import Society, Worker


Expand All @@ -10,7 +10,7 @@ class WorkersModelAdmin(admin.ModelAdmin):
# We have a customized widget form that we should use here, see ../forms/worker.py
form = WorkerForm
inlines = [ShiftInline]
list_filter = ('society',)
list_filter = ('societies',)
list_display = ('__str__', 'address', 'person_id', 'account_no', 'norlonn_number')

def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Expand Down
38 changes: 38 additions & 0 deletions spbm/apps/society/fixtures/Employment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"model": "society.employment",
"pk": 1,
"fields": {
"worker": 1,
"society": 1,
"active": true
}
},
{
"model": "society.employment",
"pk": 2,
"fields": {
"worker": 2,
"society": 1,
"active": true
}
},
{
"model": "society.employment",
"pk": 3,
"fields": {
"worker": 3,
"society": 2,
"active": true
}
},
{
"model": "society.employment",
"pk": 4,
"fields": {
"worker": 4,
"society": 2,
"active": true
}
}
]
14 changes: 5 additions & 9 deletions spbm/apps/society/fixtures/Worker.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,47 @@
"model": "society.worker",
"pk": 1,
"fields": {
"society": 1,
"active": true,
"name": "Ola S. Normann",
"address": "Olegata 43b, 0123 Oslo",
"account_no": "99002279610",
"person_id": "01020312345",
"norlonn_number": 1337
"norlonn_number": 101
}
},
{
"model": "society.worker",
"pk": 2,
"fields": {
"society": 1,
"active": true,
"name": "James McMay",
"address": "Jameston Street",
"account_no": "12345678903",
"person_id": "01010112345",
"norlonn_number": 1338
"person_id": "01020399127",
"norlonn_number": 102
}
},
{
"model": "society.worker",
"pk": 3,
"fields": {
"society": 2,
"active": true,
"name": "Kari Normann",
"address": "Trondheimsveien 235, 0586 Oslo",
"account_no": "",
"person_id": "01020334589",
"person_id": "01020399631",
"norlonn_number": null
}
},
{
"model": "society.worker",
"pk": 4,
"fields": {
"society": 2,
"active": true,
"name": "Nicolas H. Normann",
"address": "Bygata 2, 1378 Nesøya",
"account_no": "",
"person_id": "01020334589",
"person_id": "01020399712",
"norlonn_number": null
}
}
Expand Down
2 changes: 1 addition & 1 deletion spbm/apps/society/forms/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Meta:

def __init__(self, *args, **kwargs):
super(ShiftForm, self).__init__(*args, initial={'wage': society.default_wage}, **kwargs)
self.fields['worker'].queryset = Worker.objects.filter(society=society, active=True)
self.fields['worker'].queryset = Worker.objects.filter(societies__exact=society, active=True)

return ShiftForm

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
from django.forms import ModelForm
from django.forms import ModelForm, Form
from django.utils.translation import ugettext_lazy as _
from localflavor.no.forms import NOSocialSecurityNumber

from ..models import Worker


def person_id(**kwargs):
return NOSocialSecurityNumber(label=_('National ID'),
help_text=_('National social security ID, 11 digits.'),
**kwargs)


class WorkerForm(ModelForm):
"""
Form for creating a new worker.
"""
# This overwrites the meta definition.
person_id = NOSocialSecurityNumber(required=False,
label=_('National ID'),
help_text=_('National social security ID, 11 digits.'))
person_id = person_id(required=False)

class Meta:
model = Worker
fields = ['name', 'address', 'account_no', 'person_id', 'norlonn_number']


class WorkerPersonIDForm(Form):
"""
Form for dealing with workers person IDs, such as looking up by them.
"""
person_id = person_id(required=True)


class WorkerEditForm(WorkerForm):
"""
Form for editing an already existing worker.
Expand Down
74 changes: 74 additions & 0 deletions spbm/apps/society/migrations/0002_alter_and_create_employment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import django
from django.db import migrations, models


def create_employment(apps, schema_editor):
""" One-way employment creation from workers """
Worker = apps.get_model("society", "Worker")
Employment = apps.get_model("society", "Employment")
db_alias = schema_editor.connection.alias
for worker in Worker.objects.using(db_alias).all():
Employment.objects.create(worker=worker, society=worker.society, active=worker.active)


class Migration(migrations.Migration):
dependencies = [
('society', '0001_squashed_app_merges'),
]

operations = [
migrations.AlterField(
model_name='event',
name='date',
field=models.DateField(help_text='The date of the event in the format of <em>YYYY-MM-DD</em>.', verbose_name='event date'),
),
migrations.AlterField(
model_name='event',
name='name',
field=models.CharField(help_text='Name or title describing the event.', max_length=100, verbose_name='name'),
),
migrations.AlterField(
model_name='event',
name='registered',
field=models.DateField(auto_now_add=True, help_text='Date of event registration.', verbose_name='registered'),
),
migrations.AlterField(
model_name='shift',
name='hours',
field=models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(0.25)], verbose_name='hours'),
),
migrations.AlterField(
model_name='shift',
name='wage',
field=models.DecimalField(decimal_places=2, max_digits=10, validators=[django.core.validators.MinValueValidator(10)], verbose_name='wage'),
),
migrations.CreateModel(
name='Employment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('active', models.BooleanField(default=True)),
('society', models.ForeignKey(on_delete=models.PROTECT, to='society.Society')),
],
),
migrations.AddField(
model_name='worker',
name='societies',
field=models.ManyToManyField(related_name='workers', through='society.Employment', to='society.Society'),
),
migrations.AddField(
model_name='employment',
name='worker',
field=models.ForeignKey(on_delete=models.CASCADE, to='society.Worker'),
),
migrations.AlterUniqueTogether(
name='employment',
unique_together=set([('worker', 'society')]),
),
migrations.RunPython(
create_employment,
),
migrations.RemoveField(
model_name='worker',
name='society',
),
]
27 changes: 22 additions & 5 deletions spbm/apps/society/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ class WorkerManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('society')

society = models.ForeignKey(Society,
verbose_name=_('society'),
on_delete=models.PROTECT,
related_name="workers")

societies = models.ManyToManyField(Society,
through='Employment',
related_name='workers')
active = models.BooleanField(default=True,
verbose_name=_('active'),
help_text=_('Check off if the worker is still actively working.'))
Expand Down Expand Up @@ -81,6 +81,22 @@ def get_queryset(self):
def __str__(self):
return self.name + " (" + self.society.shortname + ")"

class Employment(models.Model):
"""
Through-model for the relationship between a worker and a society.
:keyword !Society
:keyword !Worker
"""

class Meta:
unique_together = ('worker', 'society')

worker = models.ForeignKey(Worker,
on_delete=models.CASCADE)
society = models.ForeignKey(Society,
on_delete=models.PROTECT)
active = models.BooleanField(default=True, null=False)


class Invoice(models.Model):
"""
Expand Down Expand Up @@ -249,8 +265,9 @@ class Meta:
norlonn_report.short_description = "Sent to norlønn"

def clean(self):
""" Cleans the shift, making sure it isn't attached to a worker not part of the society """
try:
if self.worker.society != self.event.society:
if self.event.society not in self.worker.societies.all():
raise ValidationError(_('Worker on shift does not belong to the same society as the event.'))
except:
raise ValidationError(_('You must select a worker for this shift.'))
Expand Down
2 changes: 1 addition & 1 deletion spbm/apps/society/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.conf import settings
from django.test import SimpleTestCase

test_fixtures = ['Event', 'Invoice', 'Shift', 'Society', 'Worker', 'NorlonnReport', 'User',
test_fixtures = ['Event', 'Invoice', 'Shift', 'Society', 'Worker', 'Employment', 'NorlonnReport', 'User',
'SpfUser']

# Turn on the django-jinja template debug to provide response.context in the client.
Expand Down
46 changes: 46 additions & 0 deletions spbm/apps/society/tests/test_workers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django.contrib.auth.models import User, Permission
from django.test import TestCase
from django.urls import reverse

from spbm.apps.accounts.models import SpfUser
from spbm.apps.society.models import Society, Worker
from . import test_fixtures


class WorkerTests(TestCase):
fixtures = test_fixtures

def setUp(self):
# Logging into the site as a test in itself can be somewhere else. This doesn't need it.
self.user = User(username='cyb')
self.user.save()
self.spf_user = SpfUser(user=self.user, society=Society.objects.get(pk=2))
self.spf_user.save()
self.client.force_login(self.user)

@classmethod
def setUpTestData(cls):
cls.test = True
cls.society_name = 'CYB'

def test_create_worker(self):
# TODO: finish test
worker = {
'name': 'Ola Normann',
'norlonn_number': '100',
'person_id': '01020394311',

}
self.assertEqual(self.client.post(reverse('worker-create', args=[self.society_name]), data=worker).status_code,
403)
self.user.user_permissions.add(Permission.objects.get(codename='add_worker'))
last_worker = Worker.objects.last()
post = self.client.post(reverse('worker-create', args=[self.society_name]), data=worker)
self.assertNotEqual(last_worker, Worker.objects.last())
self.assertEqual(post.status_code, 200)

def test_create_worker_duplicate_norlonn(self):
pass

def test_create_worker_duplicate_national_id(self):
pass
15 changes: 11 additions & 4 deletions spbm/helpers/auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
def user_allowed_society(usr, soc):
if usr.is_superuser:
def user_allowed_society(user, target_society):
# Superusers go straight ahead
if user.is_superuser:
return True

if usr.spfuser.society == soc:
# Checking that we're the same society
if user.spfuser.society == target_society:
return True

# Checking whether the user is one of the societies
if hasattr(target_society, 'all') and user.spfuser.society in target_society.all():
return True

return False


def user_society(request):
return request.user.spfuser.society
return request.user.spfuser.society

0 comments on commit c674575

Please sign in to comment.