diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4222609531b74ee07ca97ff4f53cc1711c95839c..e88765a522ee299d86ed6d2965ead5ef22035c74 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,8 @@ New features * Two-factor authentication with TOTP (Google Authenticator), Yubikey, SMS and phone call. - +* Devs: CRUDMixin provides a crud_event relation that returns all CRUD + events for an object `1.0a2`_ -------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1837bdd5b6d0d5fc97e320aeee7e40d55f569e11..25557e7e06ce82b323a83889f07a7a9ab444e350 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -54,6 +54,15 @@ Commit messages Commit messages should be written as described in `How to Write a Git Commit Message`_. +Commit messages should mention or even close any related issues. For merely +mentioning progress on an issue, use the keyword `advances`; for closing an +issue, use `closes`; for referring to a related issue for informational +purposes, use `cf.`. This should be done in the body of the commit message. + +The subject of a commit message can (and should) be prepended with a tag in +square brackets if it relates to a certain part of the repository, e.g. [CI] +when changing CI/CD configuration or support code, [Dev] when changing +something in the development utilities, etc. Manifestos governing development -------------------------------- diff --git a/biscuit/core/apps.py b/biscuit/core/apps.py index 2d6a4873e4da00c4d6dd7cd83dd0e100230e3e45..b40f987a4274c49df092a8535ce425bd848ca55f 100644 --- a/biscuit/core/apps.py +++ b/biscuit/core/apps.py @@ -1,10 +1,8 @@ from glob import glob import os -from warnings import warn -from django.apps import AppConfig, apps +from django.apps import AppConfig from django.conf import settings -from django.db.utils import ProgrammingError class CoreConfig(AppConfig): @@ -19,14 +17,5 @@ class CoreConfig(AppConfig): # Ignore because old is better than nothing pass # noqa - def setup_data(self) -> None: - try: - apps.get_model('otp_yubikey', 'ValidationService').objects.update_or_create( - name='default', defaults={'use_ssl': True, 'param_sl': '', 'param_timeout': ''} - ) - except ProgrammingError: - warn('Yubikey validation service could not be created yet. If you are currently in a migration, this is expected.') - def ready(self) -> None: self.clean_scss() -# self.setup_data() diff --git a/biscuit/core/migrations/0001_initial.py b/biscuit/core/migrations/0001_initial.py index d0c78a5ae09b6a42c6721bc7d64b741e94ad6877..1a88ae010c8497c6e432931ec15438feda3e0aa1 100644 --- a/biscuit/core/migrations/0001_initial.py +++ b/biscuit/core/migrations/0001_initial.py @@ -1,6 +1,5 @@ # Generated by Django 2.2.5 on 2019-09-03 18:30 -import biscuit.core.util.core_helpers from django.conf import settings from django.db import migrations, models from django.utils.translation import ugettext_lazy as _ @@ -70,7 +69,7 @@ class Migration(migrations.Migration): ('import_ref', models.CharField(blank=True, editable=False, max_length=64, null=True, verbose_name='Reference ID of import source')), ('guardians', models.ManyToManyField(related_name='children', to='core.Person', verbose_name='Guardians / Parents')), ('primary_group', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.Group')), - ('school', models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School')), + ('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='person', to=settings.AUTH_USER_MODEL)), ], options={ @@ -96,7 +95,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='group', name='school', - field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School'), ), migrations.AlterUniqueTogether( name='group', diff --git a/biscuit/core/migrations/0002_school_term.py b/biscuit/core/migrations/0002_school_term.py index e3d25010df8d9f81116e2c5d1ccb7b7516a3f9bd..026d7e3a518fe4a4d299ca01500605afbdced4ca 100644 --- a/biscuit/core/migrations/0002_school_term.py +++ b/biscuit/core/migrations/0002_school_term.py @@ -1,6 +1,5 @@ # Generated by Django 2.2.5 on 2019-09-14 12:55 -import biscuit.core.util.core_helpers from django.db import migrations, models import django.db.models.deletion from django.utils.translation import ugettext_lazy as _ @@ -32,7 +31,7 @@ class Migration(migrations.Migration): ('caption', models.CharField(max_length=30, verbose_name='Visible caption of the term')), ('date_start', models.DateField(null=True, verbose_name='Effective start date of term')), ('date_end', models.DateField(null=True, verbose_name='Effective end date of term')), - ('school', models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School')), + ('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), ], options={ 'abstract': False, diff --git a/biscuit/core/migrations/0004_yubi_otp.py b/biscuit/core/migrations/0004_yubi_otp.py new file mode 100644 index 0000000000000000000000000000000000000000..fa7c40346d627fbbe00f27122a979941334a5e86 --- /dev/null +++ b/biscuit/core/migrations/0004_yubi_otp.py @@ -0,0 +1,19 @@ +from django.db import migrations + + +def create_validation_service(apps, schema_editor): + apps.get_model('otp_yubikey', 'ValidationService').objects.update_or_create( + name='default', defaults={'use_ssl': True, 'param_sl': '', 'param_timeout': ''} + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_school_logo'), + ('otp_yubikey', '0001_initial'), + ] + + operations = [ + migrations.RunPython(create_validation_service), + ] diff --git a/biscuit/core/mixins.py b/biscuit/core/mixins.py index 024dd4870d076da301dd3c51be1a0e603e399144..9ec842a5ea99b793c688a63a9a2e8c1d5d9203b4 100644 --- a/biscuit/core/mixins.py +++ b/biscuit/core/mixins.py @@ -6,8 +6,6 @@ from django.db.models import QuerySet from easyaudit.models import CRUDEvent -from .util.core_helpers import get_current_school - class ExtensibleModel(object): """ Allow injection of code from BiscuIT apps to extend model functionality. @@ -69,16 +67,10 @@ class ExtensibleModel(object): cls._safe_add(func, func.__name__) - -class SchoolRelated(models.Model): +class CRUDMixin(models.Model): class Meta: abstract = True -# objects = SchoolRelatedManager() - - school = models.ForeignKey( - 'core.School', on_delete=models.CASCADE, default=get_current_school) - @property def crud_events(self) -> QuerySet: """Get all CRUD events connected to this object from easyaudit.""" diff --git a/biscuit/core/models.py b/biscuit/core/models.py index 6e1f6fb032dabea0c775cea3a0e45c315f5e4cb8..02071d09d4ae6cf04228be86ea3c5dd417234ace 100644 --- a/biscuit/core/models.py +++ b/biscuit/core/models.py @@ -8,7 +8,7 @@ import dbsettings from image_cropping import ImageCropField, ImageRatioField from phonenumber_field.modelfields import PhoneNumberField -from .mixins import ExtensibleModel, SchoolRelated +from .mixins import ExtensibleModel class ThemeSettings(dbsettings.Group): @@ -44,7 +44,7 @@ class School(models.Model): ordering = ['name', 'name_official'] -class SchoolTerm(SchoolRelated): +class SchoolTerm(models.Model): """ Information about a term (limited time frame) that data can be linked to. """ @@ -58,13 +58,12 @@ class SchoolTerm(SchoolRelated): 'Effective end date of term'), null=True) -class Person(SchoolRelated, ExtensibleModel): +class Person(models.Model, ExtensibleModel): """ A model describing any person related to a school, including, but not limited to, students, teachers and guardians (parents). """ class Meta: - unique_together = [['school', 'short_name'], ['school', 'import_ref']] ordering = ['last_name', 'first_name'] SEX_CHOICES = [ @@ -84,7 +83,7 @@ class Person(SchoolRelated, ExtensibleModel): 'Additional name(s)'), max_length=30, blank=True) short_name = models.CharField(verbose_name=_( - 'Short name'), max_length=5, blank=True, null=True) + 'Short name'), max_length=5, blank=True, null=True, unique=True) street = models.CharField(verbose_name=_( 'Street'), max_length=30, blank=True) @@ -110,7 +109,8 @@ class Person(SchoolRelated, ExtensibleModel): photo_cropping = ImageRatioField('photo', '600x800', size_warning=True) import_ref = models.CharField(verbose_name=_( - 'Reference ID of import source'), max_length=64, blank=True, null=True, editable=False) + 'Reference ID of import source'), max_length=64, + blank=True, null=True, editable=False, unique=True) guardians = models.ManyToManyField('self', verbose_name=_('Guardians / Parents'), symmetrical=False, related_name='children') @@ -146,19 +146,18 @@ class Person(SchoolRelated, ExtensibleModel): return self.full_name -class Group(SchoolRelated, ExtensibleModel): +class Group(models.Model, ExtensibleModel): """Any kind of group of persons in a school, including, but not limited classes, clubs, and the like. """ class Meta: - unique_together = [['school', 'name'], ['school', 'short_name']] ordering = ['short_name', 'name'] name = models.CharField(verbose_name=_( - 'Long name of group'), max_length=60) + 'Long name of group'), max_length=60, unique=True) short_name = models.CharField(verbose_name=_( - 'Short name of group'), max_length=16) + 'Short name of group'), max_length=16, unique=True) members = models.ManyToManyField('Person', related_name='member_of') owners = models.ManyToManyField('Person', related_name='owner_of') diff --git a/biscuit/core/util/core_helpers.py b/biscuit/core/util/core_helpers.py index bf19da058ae245bf4e9f47247c40786ab1d2d761..e8c0b1fe28c167b25f39eae946077c2d28832723 100644 --- a/biscuit/core/util/core_helpers.py +++ b/biscuit/core/util/core_helpers.py @@ -29,10 +29,6 @@ def get_app_packages() -> Sequence[str]: return pkgs -def get_current_school() -> int: - return 1 - - def is_impersonate(request: HttpRequest) -> bool: if hasattr(request, 'user'): return getattr(request.user, 'is_impersonate', False) diff --git a/biscuit/core/views.py b/biscuit/core/views.py index a32b9abe19b3131304c0a568d643a8336acd70d1..585d049ae72bd65c0a8ffd16bba88056850f2f04 100644 --- a/biscuit/core/views.py +++ b/biscuit/core/views.py @@ -10,7 +10,7 @@ from django_cron.models import CronJobLog from .decorators import admin_required from .forms import PersonsAccountsFormSet, EditPersonForm, EditGroupForm, EditSchoolForm, EditTermForm -from .models import Person, Group +from .models import Person, Group, School from .tables import PersonsTable, GroupsTable from .util import messages @@ -198,7 +198,7 @@ def school_management(request: HttpRequest) -> HttpResponse: def edit_school(request: HttpRequest) -> HttpResponse: context = {} - school = request.user.person.school + school = School.objects.first() edit_school_form = EditSchoolForm(request.POST or None, request.FILES or None, instance=school) context['school'] = school @@ -219,7 +219,7 @@ def edit_school(request: HttpRequest) -> HttpResponse: def edit_schoolterm(request: HttpRequest) -> HttpResponse: context = {} - term = request.user.person.school.current_term + term = School.objects.first().current_term edit_term_form = EditTermForm(request.POST or None, instance=term) if request.method == 'POST': diff --git a/dev.sh b/dev.sh index aa951f4d73254b51fbab0eda25a4521678a28b35..6a183f4000d6ce6e9ccf71c186e35193ec1a34c7 100755 --- a/dev.sh +++ b/dev.sh @@ -17,6 +17,7 @@ case "$1" in remove_pip_metadata poetry run ./manage.py migrate poetry run ./manage.py compilemessages + poetry run ./manage.py yarn install poetry run ./manage.py collectstatic --no-input set +e ;; diff --git a/docs/dev/01_setup.rst b/docs/dev/01_setup.rst index c036f5c831a0b18f0c43bbb71a75b6b5edb475b0..8d4cd0d8b9cfae6c631d7803eec7565274f5149d 100644 --- a/docs/dev/01_setup.rst +++ b/docs/dev/01_setup.rst @@ -9,6 +9,7 @@ Poetry makes a lot of stuff very easy, especially managing a virtual environment that contains BiscuIT and everything you need to run the framework and selected apps. +Also, `Yarn`_ is needed to resolve JavaScript dependencies. Get the source tree ------------------- @@ -16,7 +17,7 @@ Get the source tree To download BiscuIT and all officially bundled apps in their development version, use Git like so:: - git clone --recurse-submodules https://edugit.org/Teckids/BiscuIT/BiscuIT-ng + git clone --recurse-submodules https://edugit.org/BiscuIT/BiscuIT-ng If you do not want to download the bundled apps, leave out the ``--recurse-submodules`` option. @@ -45,13 +46,36 @@ installing BiscuIT is a matter of:: poetry install -Running commands in the virtual environment -------------------------------------------- +Regular tasks +------------- -To run commands in the virtual environment, use Poetry's ``run`` -command:: +After making changes to the environment, e.g. installing apps or updates, +some maintenance tasks need to be done: - poetry run ./manage.py runserver +1. Download and install JavaScript dependencies +2. Collect static files +3. Run database migrations + +All three steps can be done with the ``poetry run`` command and +``manage.py``:: + + poetry run ./manage.py yarn install + poetry run ./manage.py collectstatic + poetry run ./manage.py migrate + +(You might need database settings for the `migrate` command; see below.) + +Running the development server +------------------------------ + +The development server can be started using Django's ``runserver`` command. +You can either configure BiscuIT like in a production environment, or pass +basic settings in as environment variable. Here is an example that runs the +development server against a local PostgreSQL database with password +`biscuit` (all else remains default) and with the `debug` setting enabled:: + + BISCUIT_debug=true BISCUIT_database__password=biscuit poetry run ./manage.py runserver .. _Poetry: https://poetry.eustace.io/ .. _Poetry installation methods: https://poetry.eustace.io/docs/#installation +.. _Yarn: https://yarnpkg.com diff --git a/docs/dev/02_install_apps.rst b/docs/dev/02_install_apps.rst index 9ecfcc595f24cf6a6577264aea617ce2088c1c37..b083baf6cb5710db1a84a7cb8255ee678e9db07e 100644 --- a/docs/dev/02_install_apps.rst +++ b/docs/dev/02_install_apps.rst @@ -17,11 +17,5 @@ This will install the Exlibris app (library management) app by using a shell for first ``cd``'ing into the app directory and then using poetry to install the app. - -Migrate the database --------------------- - -After installing or updating any apps, the database must be updated as -well by running Django's ``migrate`` command:: - - poetry run ./manage.py migrate +DO not forget to run the maintenance tasks described earlier after +installign any app. diff --git a/docs/dev/03_run.rst b/docs/dev/03_run.rst deleted file mode 100644 index ab28dc471cf7d90a74a1602c5eb5a571d131797a..0000000000000000000000000000000000000000 --- a/docs/dev/03_run.rst +++ /dev/null @@ -1,10 +0,0 @@ -Running BiscuIT in development mode -=================================== - -Using Django's development server ---------------------------------- - -After you installed the framework and all desired apps and migrated -the database, you are ready to run BiscuIT:: - - poetry run ./manage.py runserver