diff --git a/.gitignore b/.gitignore index fcc12cfa5af171b811f3f92ddc29a783364d538c..4e43c2cca29a68a028594844be13bbe590de8ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,8 @@ docs/_build/ # Generated files biscuit/static/ +biscuit/node_modules/ + +.coverage +.tox/ +maintenance_mode_state.txt 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/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 0000000000000000000000000000000000000000..9618c5f7d4285f366f05a5f8b2fd4cef43cf829b --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,144 @@ +Contributor Covenant Code of Conduct +==================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in +our community a harassment-free experience for everyone, regardless of +age, body size, visible or invisible disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, +welcoming, diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our + mistakes, and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political + attacks +- Public or private harassment +- Publishing others’ private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our +standards of acceptable behavior and will take appropriate and fair +corrective action in response to any behavior that they deem +inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other +contributions that are not aligned to this Code of Conduct, and will +communicate reasons for moderation decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also +applies when an individual is officially representing the community in +public spaces. Examples of representing our community include using an +official e-mail address, posting via an official social media account, +or acting as an appointed representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may +be reported to the community leaders responsible for enforcement at +foss@teckids.org. All complaints will be reviewed and investigated +promptly and fairly. + +All community leaders are obligated to respect the privacy and security +of the reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in +determining the consequences for any action they deem in violation of +this Code of Conduct: + +1. Correction +~~~~~~~~~~~~~ + +**Community Impact**: Use of inappropriate language or other behavior +deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, +providing clarity around the nature of the violation and an explanation +of why the behavior was inappropriate. A public apology may be +requested. + +2. Warning +~~~~~~~~~~ + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, for a specified period of +time. This includes avoiding interactions in community spaces as well as +external channels like social media. Violating these terms may lead to a +temporary or permanent ban. + +3. Temporary Ban +~~~~~~~~~~~~~~~~ + +**Community Impact**: A serious violation of community standards, +including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No +public or private interaction with the people involved, including +unsolicited interaction with those enforcing the Code of Conduct, is +allowed during this period. Violating these terms may lead to a +permanent ban. + +4. Permanent Ban +~~~~~~~~~~~~~~~~ + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of +individuals. + +**Consequence**: A permanent ban from any sort of public interaction +within the project community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor +Covenant <https://www.contributor-covenant.org>`__, version 2.0, +available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by `Mozilla’s code of conduct +enforcement ladder <https://github.com/mozilla/diversity>`__. + +For answers to common questions about this code of conduct, see the FAQ +at https://www.contributor-covenant.org/faq. Translations are available +at https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000000000000000000000000000000000000..74f35ea4898a7e78de7d9927504601da7f442aee --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,108 @@ +Development principles and contribution guidelines +================================================== + +In order to create a high-quality software product, the BiscuIT developers +have agreed upon fundamental principles governing the code layout, coding +style and repository management for BiscuIT and all official apps. + + +Coding layout and style +----------------------- + +The coding style is defined in `PEP 8`_, with the following differences and +decisions: + +- The maximum line length is 100 characters +- Imports are structured in five blocks, each of them sorted as defined in + PEP 8: + + 1. Standard library imports + 2. Django imports + 3. Third-party imports + 4. Imports from other BiscuIT apps (absolute imports) + 5. Imports from the same BiscuIT app (realtive imports) + +- All string literals use single quotes + +For the layout of source trees and style recommendations specific to Django, +the `Django coding style`_ is a good source of information, together with +the `Django Best Practices`_ collection. + + +Working with the Git repository +------------------------------- + +The Git repository shall be used as a historic documentation of development +and as change management. It is important that the Git commit history +describes waht was changed, by whom and why. + +Feature branches +~~~~~~~~~~~~~~~~ + +All features and bug fixes should be developed in their own branch and later +merged into the master branch as a whole. Of course, sometimes, it is +sensible to not do that, e.g. for fixing mere typos and the like + +WIthin the feature branch, every logical step should be commited separately. +It is neither required nor desired to do micro-commits about every +development step. The commit history should describe the trains of thought +the design and implementation is based on. + +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 +-------------------------------- + +The FOSS community has created some manifestos describing several aspects of +software development, to agree upon a baseline for these aspects. The +BiscuIT developers have agreed to adhere to the following manifestos: + +- The `Sane software manifesto`_ +- The `Accessibility Manifesto`_ +- The `User Data Manifesto`_ + +Not all theses from these manifestos are applicable. For example, most data +about persons in a school information system are dictated by the school and +probably governed by laws defining what and when to store. In that case, +giving the user control over these decisions is not possible. Developers +need to decide what should resonably be followed. + + +Text documents +-------------- + +If there is no objective reason against it, all text documents accompanying +the source use `reStructuredText`_. + + +Contributing to upstream +------------------------ + +If possible and reasonable, code that can be of use to others in the general +Django ecosystem shall be contributed to any upstream dependency, or a new +generalised upstream dependency be created, under the most permissive +licence possible. + + +.. _PEP 8: https://pep8.org/ +.. _Django coding style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/ +.. _Django Best Practices: https://django-best-practices.readthedocs.io/en/latest/index.html +.. _How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/ +.. _Sane software manifesto: https://sane-software.globalcode.info/ +.. _Accessibility Manifesto: http://accessibilitymanifesto.com/ +.. _User Data Manifesto: https://userdatamanifesto.org/ +.. _reStructuredText: http://docutils.sourceforge.net/rst.html diff --git a/Dockerfile b/Dockerfile index 19605c6bfe73ea9ea2ef0eb71998a1c4e3d0f9b5..ec6f771d02496a26629df0f0959af7ff7cbf69c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,11 +21,11 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends \ build-essential \ gettext \ - libjs-bootstrap4 \ libpq5 \ libpq-dev \ libssl-dev \ - netcat-openbsd + netcat-openbsd \ + yarnpkg # Install core dependnecies WORKDIR /usr/src/app @@ -42,6 +42,7 @@ RUN mkdir -p /var/lib/biscuit/media /var/lib/biscuit/static /var/lib/biscuit/bac # Build messages and assets RUN python manage.py compilemessages; \ + python manage.py yarn install; \ python manage.py collectstatic --no-input --clear # Clean up build dependencies @@ -50,12 +51,14 @@ RUN apt-get remove --purge -y \ gettext \ libpq-dev \ libssl-dev \ - python3-dev; \ + yarnpkg; \ apt-get autoremove --purge -y; \ apt-get clean -y; \ pip uninstall -y poetry; \ rm -f /var/lib/apt/lists/*_*; \ - rm -rf /root/.cache + rm -rf /root/.cache; \ + rm -rf biscuit/node_modules; \ + rm -rf /usr/local/lib/node_modules # Declare a persistent volume for all data VOLUME /var/lib/biscuit diff --git a/MANIFEST.in b/MANIFEST.in index 01c061d501e1dc451f69f0e7524590e3fb789755..9145ce4a03a7244a2637966e38c631672d9531ff 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,8 @@ +include CODE_OF_CONDUCT.rst +include CONTRIBUTING.rst include LICENCE +include manage.py recursive-include biscuit/core/static * recursive-include biscuit/core/templates * recursive-include biscuit/core/migrations * +recursive-include docs * diff --git a/apps/official/BiscuIT-App-Alsijil b/apps/official/BiscuIT-App-Alsijil index a52358af2722329ae43916adee4b93208991ebc6..9014661e518c6d9457723571d7b0ef1ac9be6c6b 160000 --- a/apps/official/BiscuIT-App-Alsijil +++ b/apps/official/BiscuIT-App-Alsijil @@ -1 +1 @@ -Subproject commit a52358af2722329ae43916adee4b93208991ebc6 +Subproject commit 9014661e518c6d9457723571d7b0ef1ac9be6c6b diff --git a/apps/official/BiscuIT-App-Chronos b/apps/official/BiscuIT-App-Chronos index c3dffaa6d1cd91af1f79103e71286e11daf4d62d..d4da734afa405a6af2adbe607f553e2b759737d6 160000 --- a/apps/official/BiscuIT-App-Chronos +++ b/apps/official/BiscuIT-App-Chronos @@ -1 +1 @@ -Subproject commit c3dffaa6d1cd91af1f79103e71286e11daf4d62d +Subproject commit d4da734afa405a6af2adbe607f553e2b759737d6 diff --git a/apps/official/BiscuIT-App-Exlibris b/apps/official/BiscuIT-App-Exlibris index f18823b68731884e9ed661ce92474a54708a6f72..b03369df8214f062a18a70824c6a257cf4ab427d 160000 --- a/apps/official/BiscuIT-App-Exlibris +++ b/apps/official/BiscuIT-App-Exlibris @@ -1 +1 @@ -Subproject commit f18823b68731884e9ed661ce92474a54708a6f72 +Subproject commit b03369df8214f062a18a70824c6a257cf4ab427d diff --git a/biscuit/core/admin.py b/biscuit/core/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..0810d4504654e0e1337c70810c41696d6e2abbd0 --- /dev/null +++ b/biscuit/core/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from .models import Group, Person, School, SchoolTerm + + +admin.site.register(Person) +admin.site.register(Group) +admin.site.register(School) +admin.site.register(SchoolTerm) diff --git a/biscuit/core/apps.py b/biscuit/core/apps.py index 78aba32f89cab4c759e912dd7378459ac17f26de..ef62cfb3085fa7d763f4506bc94613fcc77c7c53 100644 --- a/biscuit/core/apps.py +++ b/biscuit/core/apps.py @@ -1,32 +1,13 @@ -from glob import glob -import os -from warnings import warn - from django.apps import AppConfig, apps -from django.conf import settings -from django.db.utils import ProgrammingError +from django.db.models.signals import post_save + +from .signals import clean_scss class CoreConfig(AppConfig): name = 'biscuit.core' verbose_name = 'BiscuIT - The Free School Information System' - def clean_scss(self) -> None: - for source_map in glob(os.path.join(settings.STATIC_ROOT, '*.css.map')): - try: - os.unlink(source_map) - except OSError: - # 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() + clean_scss() + post_save.connect(clean_scss, sender=apps.get_model('dbsettings', 'Setting')) 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/migrations/0005_unlink_school.py b/biscuit/core/migrations/0005_unlink_school.py new file mode 100644 index 0000000000000000000000000000000000000000..de030eb21bcf2d30a1537497e16f76f3fbc439d5 --- /dev/null +++ b/biscuit/core/migrations/0005_unlink_school.py @@ -0,0 +1,53 @@ +# Generated by Django 2.2.8 on 2019-12-09 08:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_yubi_otp'), + ] + + operations = [ + migrations.RemoveField( + model_name='schoolterm', + name='school', + ), + migrations.AlterField( + model_name='group', + name='name', + field=models.CharField(max_length=60, unique=True, verbose_name='Long name of group'), + ), + migrations.AlterField( + model_name='group', + name='short_name', + field=models.CharField(max_length=16, unique=True, verbose_name='Short name of group'), + ), + migrations.AlterField( + model_name='person', + name='import_ref', + field=models.CharField(blank=True, editable=False, max_length=64, null=True, unique=True, verbose_name='Reference ID of import source'), + ), + migrations.AlterField( + model_name='person', + name='short_name', + field=models.CharField(blank=True, max_length=5, null=True, unique=True, verbose_name='Short name'), + ), + migrations.AlterUniqueTogether( + name='group', + unique_together=set(), + ), + migrations.AlterUniqueTogether( + name='person', + unique_together=set(), + ), + migrations.RemoveField( + model_name='group', + name='school', + ), + migrations.RemoveField( + model_name='person', + name='school', + ), + ] 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 9260fcbd3cd600240bb3967d2a6d4888a22928aa..ffd0189e1a901c1fa7349b8cf164b9da8f00b49d 100644 --- a/biscuit/core/models.py +++ b/biscuit/core/models.py @@ -4,10 +4,24 @@ from django.contrib.auth import get_user_model from django.db import models from django.utils.translation import ugettext_lazy as _ +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): + colour_primary = dbsettings.StringValue(default='#007bff') + colour_secondary = dbsettings.StringValue(default='#6c757d') + colour_success = dbsettings.StringValue(default='#28a745') + colour_info = dbsettings.StringValue(default='#17a2b8') + colour_warning = dbsettings.StringValue(default='#ffc107') + colour_danger = dbsettings.StringValue(default='#dc3545') + colour_light = dbsettings.StringValue(default='#f8f9fa') + colour_dark = dbsettings.StringValue(default='#343a40') + +theme_settings = ThemeSettings('Global theme settings') class School(models.Model): @@ -30,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. """ @@ -44,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 = [ @@ -70,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) @@ -96,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') @@ -132,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/settings.py b/biscuit/core/settings.py index 449d3483634e8acd49a0748c4320cd2ac3bf336e..61c92d3ea50a109873dbdb3e13f8e1bbd4100f9c 100644 --- a/biscuit/core/settings.py +++ b/biscuit/core/settings.py @@ -52,10 +52,12 @@ INSTALLED_APPS = [ 'sass_processor', 'easyaudit', 'dbbackup', + 'dbsettings', 'django_cron', 'bootstrap4', 'fa', 'django_any_js', + 'django_yarnpkg', 'django_tables2', 'easy_thumbnails', 'image_cropping', @@ -80,16 +82,10 @@ INSTALLED_APPS += get_app_packages() STATICFILES_FINDERS = [ 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'django_yarnpkg.finders.NodeModulesFinder', 'sass_processor.finders.CssFinder' ] -SASS_PROCESSOR_AUTO_INCLUDE = False -SASS_PROCESSOR_CUSTOM_FUNCTIONS = { - 'get-colour': 'biscuit.core.util.sass_helpers.get_colour', -} -SASS_PROCESSOR_INCLUDE_DIRS = [ - _settings.get('bootstrap.sass_path', '/usr/share/sass/bootstrap') -] MIDDLEWARE = [ # 'django.middleware.cache.UpdateCacheMiddleware', @@ -233,6 +229,7 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.1/howto/static-files/ + STATIC_URL = _settings.get('static.url', '/static/') MEDIA_URL = _settings.get('media.url', '/media/') @@ -241,41 +238,54 @@ LOGOUT_REDIRECT_URL = 'index' STATIC_ROOT = _settings.get('static.root', os.path.join(BASE_DIR, 'static')) MEDIA_ROOT = _settings.get('media.root', os.path.join(BASE_DIR, 'media')) +NODE_MODULES_ROOT = _settings.get('node_modules.root', os.path.join(BASE_DIR, 'node_modules')) + +YARN_INSTALLED_APPS = [ + 'bootstrap', + 'font-awesome', + 'jquery', + 'popper.js', + 'datatables', + 'select2' +] -FONT_AWESOME = {'url': _settings.get( - 'bootstrap.fa_url', '/javascript/font-awesome/css/font-awesome.min.css')} +JS_URL = _settings.get('js_assets.url', STATIC_URL) +JS_ROOT = _settings.get('js_assets.root', NODE_MODULES_ROOT+'/node_modules') + +FONT_AWESOME = {'url': JS_URL+'/font-awesome/css/font-awesome.min.css'} BOOTSTRAP4 = { - 'css_url': _settings.get('bootstrap.css_url', '/javascript/bootstrap4/css/bootstrap.min.css'), - 'javascript_url': _settings.get('bootstrap.js_url', '/javascript/bootstrap4/js/bootstrap.min.js'), - 'jquery_url': _settings.get('bootstrap.jquery_url', '/javascript/jquery/jquery.min.js'), - 'popper_url': _settings.get('bootstrap.popper_url', '/javascript/popper.js/umd/popper.min.js'), + 'css_url': JS_URL+'/bootstrap/dist//css/bootstrap.min.css', + 'javascript_url': JS_URL+'/bootstrap/dist/js/bootstrap.min.js', + 'jquery_url': JS_URL+'/jquery/dist/jquery.min.js', + 'popper_url': JS_URL+'/popper.js/dist/umd/popper.min.js', 'include_jquery': True, 'include_popper': True, 'javascript_in_head': True } -DATATABLES_BASE = _settings.get( - 'bootstrap.datatables_base', '/javascript/jquery-datatables') +SELECT2_CSS = JS_URL+'/select2/dist/css/select2.min.css' +SELECT2_JS = JS_URL+'/select2/dist/js/select2.min.js' +SELECT2_I18N_PATH = JS_URL+'/select2/dist/js/i18n' ANY_JS = { 'DataTables': { - 'js_url': DATATABLES_BASE + '/jquery.dataTables.min.js' + 'js_url': JS_URL+'/datatables/media/js/jquery.dataTables.min.js' }, 'DataTables-Bootstrap4': { - 'css_url': DATATABLES_BASE + '/css/dataTables.bootstrap4.min.css', - 'js_url': DATATABLES_BASE + '/dataTables.bootstrap4.min.js' + 'css_url': JS_URL+'/datatables/media/css/dataTables.bootstrap4.min.css', + 'js_url': JS_URL+'/datatables/media/js/dataTables.bootstrap4.min.js' } } -COLOUR_PRIMARY = _settings.get('theme.colours.primary', '#007bff') -COLOUR_SECONDARY = _settings.get('theme.colours.secondary', '#6c757d') -COLOUR_SUCCESS = _settings.get('theme.colours.success', '#28a745') -COLOUR_INFO = _settings.get('theme.colours.info', '#17a2b8') -COLOUR_WARNING = _settings.get('theme.colours.warning', '#ffc107') -COLOUR_DANGER = _settings.get('theme.colours.danger', '#dc3545') -COLOUR_LIGHT = _settings.get('theme.colours.light', '#f8f9fa') -COLOUR_DARK = _settings.get('theme.colours.dark', '#343a40') +SASS_PROCESSOR_AUTO_INCLUDE = False +SASS_PROCESSOR_CUSTOM_FUNCTIONS = { + 'get-colour': 'biscuit.core.util.sass_helpers.get_colour', + 'get-theme-setting': 'biscuit.core.util.sass_helpers.get_theme_setting', +} +SASS_PROCESSOR_INCLUDE_DIRS = [ + _settings.get('bootstrap.sass_path', JS_ROOT+'/bootstrap/scss/') +] ADMINS = _settings.get('contact.admins', []) SERVER_EMAIL = _settings.get('contact.from', 'root@localhost') diff --git a/biscuit/core/signals.py b/biscuit/core/signals.py new file mode 100644 index 0000000000000000000000000000000000000000..e531c5b9ed0158c3986144ab68e6c0eaeea9eda9 --- /dev/null +++ b/biscuit/core/signals.py @@ -0,0 +1,13 @@ +from glob import glob +import os + +from django.conf import settings + + +def clean_scss(*args, **kwargs) -> None: + for source_map in glob(os.path.join(settings.STATIC_ROOT, '*.css.map')): + try: + os.unlink(source_map) + except OSError: + # Ignore because old is better than nothing + pass # noqa diff --git a/biscuit/core/static/bootstrap_modified.scss b/biscuit/core/static/bootstrap_modified.scss index d77585eb8d6c3137db9f0f35513e5185dbd7195a..a1e918db91287ca16e560ac3851790ba90fb6891 100644 --- a/biscuit/core/static/bootstrap_modified.scss +++ b/biscuit/core/static/bootstrap_modified.scss @@ -1,12 +1,12 @@ $theme-colors: ( - "primary": adjust-color(get-colour(get-setting(COLOUR_PRIMARY)), $alpha: 1), - "secondary": adjust-color(get-colour(get-setting(COLOUR_SECONDARY)), $alpha: 1), - "success": adjust-color(get-colour(get-setting(COLOUR_SUCCESS)), $alpha: 1), - "info": adjust-color(get-colour(get-setting(COLOUR_INFO)), $alpha: 1), - "warning": adjust-color(get-colour(get-setting(COLOUR_WARNING)), $alpha: 1), - "danger": adjust-color(get-colour(get-setting(COLOUR_DANGER)), $alpha: 1), - "light": adjust-color(get-colour(get-setting(COLOUR_LIGHT)), $alpha: 1), - "dark": adjust-color(get-colour(get-setting(COLOUR_DARK)), $alpha: 1), + "primary": adjust-color(get-colour(get-theme-setting(colour_primary)), $alpha: 1), + "secondary": adjust-color(get-colour(get-theme-setting(colour_secondary)), $alpha: 1), + "success": adjust-color(get-colour(get-theme-setting(colour_success)), $alpha: 1), + "info": adjust-color(get-colour(get-theme-setting(colour_info)), $alpha: 1), + "warning": adjust-color(get-colour(get-theme-setting(colour_warning)), $alpha: 1), + "danger": adjust-color(get-colour(get-theme-setting(colour_danger)), $alpha: 1), + "light": adjust-color(get-colour(get-theme-setting(colour_light)), $alpha: 1), + "dark": adjust-color(get-colour(get-theme-setting(colour_dark)), $alpha: 1), ); @import "bootstrap"; diff --git a/biscuit/core/tests/models/test_person.py b/biscuit/core/tests/models/test_person.py new file mode 100644 index 0000000000000000000000000000000000000000..e454d3b60709498564c1c005242694b08ad805bb --- /dev/null +++ b/biscuit/core/tests/models/test_person.py @@ -0,0 +1,13 @@ +import pytest + +from biscuit.core.models import Person + + +@pytest.mark.django_db +def test_full_name(): + _person = Person.objects.create( + first_name='Jane', + last_name='Doe' + ) + + assert _person.full_name == 'Doe, Jane' diff --git a/biscuit/core/tests/templatetags/test_data_helpers.py b/biscuit/core/tests/templatetags/test_data_helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..096903947c8c21870ad5c05fcffb5b0a8e522a12 --- /dev/null +++ b/biscuit/core/tests/templatetags/test_data_helpers.py @@ -0,0 +1,22 @@ +from biscuit.core.templatetags.data_helpers import get_dict + +def test_get_dict_object(): + class _Foo(object): + bar = 12 + + assert _Foo.bar == get_dict(_Foo, 'bar') + +def test_get_dict_dict(): + _foo = {'bar': 12} + + assert _foo['bar'] == get_dict(_foo, 'bar') + +def test_get_dict_list(): + _foo = [10, 11, 12] + + assert _foo[2] == get_dict(_foo, 2) + +def test_get_dict_invalid(): + _foo = 12 + + assert get_dict(_foo, 'bar') is None diff --git a/biscuit/core/tests/test_person.py b/biscuit/core/tests/test_person.py deleted file mode 100644 index f267e5ae0500f51c83572e5a3d0f76d80dc14cb4..0000000000000000000000000000000000000000 --- a/biscuit/core/tests/test_person.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.test import TestCase - -from biscuit.core.models import Person - - -class PersonTestCase(TestCase): - def setUp(self): - self._person = Person.objects.create( - first_name='Jane', - last_name='Doe' - ) - - def test_full_name(self): - assert self._person.full_name == 'Doe, Jane' diff --git a/biscuit/core/tests/views/test_account.py b/biscuit/core/tests/views/test_account.py new file mode 100644 index 0000000000000000000000000000000000000000..3bffcf17e9e226ac04230d367219e3cc5942a835 --- /dev/null +++ b/biscuit/core/tests/views/test_account.py @@ -0,0 +1,53 @@ +import pytest + +from django.conf import settings +from django.urls import reverse + +@pytest.mark.django_db +def test_index_not_logged_in(client): + response = client.get('/') + + assert response.status_code == 200 + assert reverse(settings.LOGIN_URL) in response.content.decode('utf-8') + +@pytest.mark.django_db +def test_login(client, django_user_model): + username = 'foo' + password = 'bar' + + django_user_model.objects.create_user(username=username, password=password) + client.login(username=username, password=password) + + response = client.get('/') + + assert response.status_code == 200 + assert reverse(settings.LOGIN_URL) not in response.content.decode('utf-8') + +@pytest.mark.django_db +def test_index_not_linked_to_person(client, django_user_model): + username = 'foo' + password = 'bar' + + django_user_model.objects.create_user(username=username, password=password) + client.login(username=username, password=password) + + response = client.get('/') + + assert response.status_code == 200 + assert 'You are not linked to a person' in response.content.decode('utf-8') + +@pytest.mark.django_db +def test_logout(client, django_user_model): + username = 'foo' + password = 'bar' + + django_user_model.objects.create_user(username=username, password=password) + client.login(username=username, password=password) + + response = client.get('/') + assert response.status_code == 200 + + response = client.get(reverse('logout'), follow=True) + + assert response.status_code == 200 + assert reverse(settings.LOGIN_URL) in response.content.decode('utf-8') diff --git a/biscuit/core/urls.py b/biscuit/core/urls.py index 414aa9b24aa78acf2f9f8e4114a65a6b8eeda585..dd98dc0175b1e6a13605208615f6fa2ca1482342 100644 --- a/biscuit/core/urls.py +++ b/biscuit/core/urls.py @@ -1,4 +1,5 @@ from django.apps import apps +from django.contrib import admin from django.conf import settings from django.conf.urls.static import static from django.contrib.auth import views as auth_views @@ -10,6 +11,7 @@ from two_factor.urls import urlpatterns as tf_urls from . import views urlpatterns = [ + path('admin/', admin.site.urls), path('data_management/', views.data_management, name='data_management'), path('status/', views.system_status, name='system_status'), path('school_management', views.school_management, name='school_management'), @@ -35,9 +37,14 @@ urlpatterns = [ path('contact/', include('contact_form.urls')), path('impersonate/', include('impersonate.urls')), path('__i18n__/', include('django.conf.urls.i18n')), - path('select2/', include('django_select2.urls')) + path('select2/', include('django_select2.urls')), + path('settings/', include('dbsettings.urls')) ] +# Serve static files from STATIC_ROOT to make it work with runserver +# collectstatic is also required in development for this +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + # Add URLs for optional features if hasattr(settings, 'TWILIO_ACCOUNT_SID'): from two_factor.gateways.twilio.urls import urlpatterns as tf_twilio_urls # noqa @@ -45,8 +52,6 @@ if hasattr(settings, 'TWILIO_ACCOUNT_SID'): # Serve javascript-common if in development if settings.DEBUG: - urlpatterns += static('/javascript/', - document_root='/usr/share/javascript/') urlpatterns.append(path('__debug__/', include(debug_toolbar.urls))) # Automatically mount URLs from all installed BiscuIT apps 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/util/sass_helpers.py b/biscuit/core/util/sass_helpers.py index aa28accf32e4cbd42711c7b69ccc2bf8e8dfbc22..c609c0ed9941c1485edfb1d7e8111c1a06b414eb 100644 --- a/biscuit/core/util/sass_helpers.py +++ b/biscuit/core/util/sass_helpers.py @@ -1,9 +1,15 @@ from colour import web2hex from sass import SassColor +from biscuit.core.models import theme_settings + def get_colour(html_colour: str) -> SassColor: rgb = web2hex(html_colour, force_long=True)[1:] r, g, b = int(rgb[0:2], 16), int(rgb[2:4], 16), int(rgb[4:6], 16) return SassColor(r, g, b, 255) + + +def get_theme_setting(setting: str) -> str: + return getattr(theme_settings, setting, '') 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/code_of_conduct.md b/code_of_conduct.md deleted file mode 100644 index d9f811167cd7975114f6cfc8efdd90bc8dd1f9cc..0000000000000000000000000000000000000000 --- a/code_of_conduct.md +++ /dev/null @@ -1,126 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, -gender identity and expression, level of experience, education, -socio-economic status, nationality, personal appearance, race, religion, or -sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards -of acceptable behavior and will take appropriate and fair corrective action -in response to any behavior that they deem inappropriate, threatening, -offensive, or harmful. - -Community leaders have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, and will communicate reasons -for moderation decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies -when an individual is officially representing the community in public -spaces. Examples of representing our community include using an official -e-mail address, posting via an official social media account, or acting as -an appointed representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -<foss@teckids.org>. All complaints will be reviewed and investigated -promptly and fairly. - -All community leaders are obligated to respect the privacy and security of -the reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in -determining the consequences for any action they deem in violation of this -Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, -providing clarity around the nature of the violation and an explanation of -why the behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external -channels like social media. Violating these terms may lead to a temporary -or permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public -or private interaction with the people involved, including unsolicited -interaction with those enforcing the Code of Conduct, is allowed during this -period. Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the project community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, -available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. 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/docker/nginx/Dockerfile b/docker/nginx/Dockerfile index 9645e8f005e8fcb5df44150d101e5166da4a60d0..e47ed67f547c508c1d18dc5ae6184f1fef90f873 100644 --- a/docker/nginx/Dockerfile +++ b/docker/nginx/Dockerfile @@ -3,7 +3,4 @@ FROM nginx RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d -RUN apt-get update && apt-get upgrade -y -RUN apt-get install -y libjs-bootstrap4 fonts-font-awesome libjs-jquery libjs-popper.js libjs-jquery-datatables - RUN mkdir /var/lib/biscuit diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf index 41f0bc39eeeb1233fdad9460ba953ca4e0dc3319..39c2ce0be8a1992be9215f64da3ce7ed02527c7d 100644 --- a/docker/nginx/nginx.conf +++ b/docker/nginx/nginx.conf @@ -19,8 +19,4 @@ server { location /static/ { alias /var/lib/biscuit/static/; } - - location /javascript/ { - alias /usr/share/javascript/; - } } 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 diff --git a/docs/dev/99_contributing.rst b/docs/dev/99_contributing.rst new file mode 100644 index 0000000000000000000000000000000000000000..a5ffcc710e824f4cdcf235f12b25914efcf5ae28 --- /dev/null +++ b/docs/dev/99_contributing.rst @@ -0,0 +1,2 @@ +.. include:: ../../CONTRIBUTING.rst +.. include:: ../../CODE_OF_CONDUCT.rst diff --git a/poetry.lock b/poetry.lock index 774884826a0bdec3bc84cb43e43a050e91dc3a3c..a4288e1562a264faa06ac0fa0fe564f5c100b045 100644 --- a/poetry.lock +++ b/poetry.lock @@ -93,8 +93,8 @@ category = "main" description = "Cross-platform colored terminal text." name = "colorama" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.4.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" [[package]] category = "main" @@ -249,6 +249,14 @@ Django = ">=1.5" pytz = "*" six = "*" +[[package]] +category = "main" +description = "Application settings whose values can be updated while a project is up and running." +name = "django-dbsettings" +optional = false +python-versions = "*" +version = "0.11.0" + [[package]] category = "main" description = "A configurable set of panels that display various debug information about the current request/response." @@ -473,6 +481,18 @@ twilio = ">=6.0" reference = "bf9d0812ab11320a6cadc6709c382a03184f2e31" type = "git" url = "https://github.com/Bouke/django-two-factor-auth" +[[package]] +category = "main" +description = "Integrate django with yarnpkg" +name = "django-yarnpkg" +optional = false +python-versions = "*" +version = "6.0.0" + +[package.dependencies] +django = "*" +six = "*" + [[package]] category = "dev" description = "Docutils -- Python Documentation Utilities" @@ -487,7 +507,7 @@ description = "The dynamic configurator for your Python Project" name = "dynaconf" optional = false python-versions = "*" -version = "2.2.0" +version = "2.2.1" [package.dependencies] PyYAML = "*" @@ -659,7 +679,7 @@ description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" -version = "8.0.0" +version = "8.0.2" [[package]] category = "dev" @@ -1207,7 +1227,7 @@ description = "Fast, Extensible Progress Meter" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.40.0" +version = "4.40.1" [[package]] category = "main" @@ -1296,7 +1316,7 @@ more-itertools = "*" ldap = ["django-auth-ldap"] [metadata] -content-hash = "3a9826926228eb3fc31663fd44bde2bcaa49f29673a679465789cb3ef316f568" +content-hash = "908e1e56f87aef8ccff2ce9a79bf335b5cb09896566d1dddb45c62c799c86310" python-versions = "^3.7" [metadata.hashes] @@ -1310,7 +1330,7 @@ beautifulsoup4 = ["5279c36b4b2ec2cb4298d723791467e3000e5384a43ea0cdf5d45207c7e97 certifi = ["017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", "25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"] chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] -colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] +colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"] colour = ["33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c", "af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee"] configobj = ["a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"] coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"] @@ -1325,6 +1345,7 @@ django-common-helpers = ["2d56be6fa261d829a6a224f189bf276267b9082a17d613fe5f015d django-contact-form = ["b42b7e04d6af3318b8427c1eaf62385ec66da252aa79b607ee55d956c7af4a2d", "c31f73faa13f52efa81ac95f41007f3a84eca617f92773a1bed7ca90c61cb3ed"] django-cron = ["08d22708c8b2ecab8cda989019a66c7e1e2424c59d822796fd45abf7731d261d"] django-dbbackup = ["9470e5d8bdaee4feb878b1b66c59eb9b27a131cccd648bf7cbfe70930acd4fc0"] +django-dbsettings = ["e3147ced54b7db3371df10df8845e4514aeae96720000bca1a01d0a6490a1404"] django-debug-toolbar = ["24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8", "77cfba1d6e91b9bc3d36dc7dc74a9bb80be351948db5f880f2562a0cbf20b6c5"] django-easy-audit = ["1c5d5e6d6a33f50f696ed53cdaf51de0a4ae2f110ef8c41b33bc139b737729a6", "4b40a30599fe721eb0a9946f5023254fa0904d531c9f4adb23ee52601efaf89b"] django-fa = ["e3ebf97b90e374b5ccb5b8a70e4a932c8787f2ee995c09a97a63bf9a1366c3ff"] @@ -1345,8 +1366,9 @@ django-settings-context-processor = ["d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd05 django-stubs = ["cd6a7333d518b9168f001b8a31c4ea89a91dea40a9dd1535c798635f69a5f80a", "e3673348a42c7259e81a4ea141dae2b2e711220ec631a6215ba9dc23cdcabdf4"] django-tables2 = ["0d9b17f5c030ba1b5fcaeb206d8397bf58f1fdfc6beaf56e7874841b8647aa94", "6afa0496695e15b332e98537265d09fe01a55b28c75a85323d8e6b0dc2350280"] django-two-factor-auth = [] +django-yarnpkg = ["010af70049cca94496d4c96ca45e62f13339edd1c22653ab8bfe055acbccd41b", "0d63c7b17e4b9c6c144c4093de3877ce70152f957b36fd7a50b259dc500a4948"] docutils = ["6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"] -dynaconf = ["117b7e52698af82a535bcb71012f3221e1ab9c869bb8163ebf156569f32ff07d", "abeb44db4249c443083584cdd4d9c5c10cd773f11067e270660d15e6eef668d7"] +dynaconf = ["52e3e41290763e405723b13a893592f8bca06f676854e59623052ffeee1a658f", "75691e9dd4093a1a2dc530d33369ae9296cfba30d29b72b00715dfb98b3f82e4"] easy-thumbnails = ["23fbe3415c93b2369ece8ebdfb5faa05540943bef8b941b3118ce769ba95e275"] entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] faker = ["202ad3b2ec16ae7c51c02904fb838831f8d2899e61bf18db1e91a5a582feab11", "92c84a10bec81217d9cb554ee12b3838c8986ce0b5d45f72f769da22e4bb5432"] @@ -1362,7 +1384,7 @@ libsass = ["003a65b4facb4c5dbace53fb0f70f61c5aae056a04b4d112a198c3c9674b31f2", " mando = ["4ce09faec7e5192ffc3c57830e26acba0fd6cd11e1ee81af0d4df0657463bd1c", "79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"] markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] -more-itertools = ["53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2", "a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45"] +more-itertools = ["b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", "c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"] mypy = ["0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", "07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", "10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", "11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", "15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", "352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", "437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", "49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", "6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", "7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", "cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"] mypy-extensions = ["090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"] packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"] @@ -1415,7 +1437,7 @@ sqlparse = ["40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", text-unidecode = ["1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", "bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"] toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] tox = ["7efd010a98339209f3a8292f02909b51c58417bfc6838ab7eca14cf90f96117a", "8dd653bf0c6716a435df363c853cad1f037f9d5fddd0abc90d0f48ad06f39d03"] -tqdm = ["156a0565f09d1f0ef8242932a0e1302462c93827a87ba7b4423d90f01befe94c", "c0ffb55959ea5f3eaeece8d2db0651ba9ced9c72f40a6cce3419330256234289"] +tqdm = ["895796ea8df435b6f502bf122f2b2034a3d48e6d8ff52175606ac1051b0e3e12", "e405d16c98fcf30725d0c9d493ed07302a18846b5452de5253030ccd18996f87"] twilio = ["da282a9c02bd9dfb190b798528b478833d8d28cb51464e8c45da0f0794384cde"] typed-ast = ["1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"] typing-extensions = ["091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", "910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", "cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"] diff --git a/pyproject.toml b/pyproject.toml index 1bcd8b1b532fac82e7bc924ba2edd4c169bbd8ed..93c9ddd9143721f55a77f4487b6abd10ebfbb6e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,8 @@ psycopg2 = "^2.8" django_select2 = "^7.1" requests = "^2.22" django-two-factor-auth = { git = "https://github.com/Bouke/django-two-factor-auth", rev = "bf9d0812ab11320a6cadc6709c382a03184f2e31", extras = [ "YubiKey", "phonenumbers", "Call", "SMS" ] } +django-yarnpkg = "^6.0" +django-dbsettings = "^0.11.0" [tool.poetry.extras] ldap = ["django-auth-ldap"]