From 7e70ee7e5195e10d6367ecc3773457b6f2501790 Mon Sep 17 00:00:00 2001 From: GansaK Date: Wed, 4 Oct 2023 22:28:55 +0200 Subject: [PATCH 1/7] Bug fix. Removed unnecessary text next to the title in webpage (#785) --- src/moore/templates/page.html | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/moore/templates/page.html b/src/moore/templates/page.html index b197c1a2..4e39a89e 100644 --- a/src/moore/templates/page.html +++ b/src/moore/templates/page.html @@ -5,14 +5,6 @@

{% firstof page.translated_title page.title %}

-
{% block content %}{% endblock %} From b444ff6ae6718ff7b70d2d782be2f5b777538732 Mon Sep 17 00:00:00 2001 From: Hampus Toft <136370606+hato1883@users.noreply.github.com> Date: Wed, 8 Nov 2023 18:02:40 +0100 Subject: [PATCH 2/7] Fix/sorted section list 467 (#790) * Sort section list by its abbreviation * Changed RegistrationForm to CustomUserCreationForm This is the same form that admin page uses when creating users. RegistrationForm is not affected by change in forms.py in commit: 9be07d310b20af2acb1f7dcaf0dc17bd6c928b26 and thereby did not get a sorted section list from previous change. I do not see the diffrence in accont creation between the two forms. * Changed forms.py Changed so that section sorting occurs on all ModelForm's. This allows us to yet again use a custom ModelForm for admin page and another for the registration page. --- src/members/forms.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/members/forms.py b/src/members/forms.py index cfcbc185..c6419f0d 100644 --- a/src/members/forms.py +++ b/src/members/forms.py @@ -24,6 +24,11 @@ class MemberForm(forms.ModelForm): + section = forms.ModelChoiceField( + required=False, + queryset=Section.objects.order_by('abbreviation'), + label=_('Section'), + ) person_number = PersonNumberField( label=_('Person number'), help_text=_('Person number using the YYYYMMDD-XXXX format.'), @@ -81,6 +86,12 @@ def save(self, commit=True): class RegistrationForm(MemberForm, auth.UserCreationForm): + section = forms.ModelChoiceField( + required=False, + queryset=Section.objects.order_by('abbreviation'), + label=_('Section'), + ) + class Meta: model = Member fields = ['username', 'email', 'phone_number', 'section'] @@ -216,6 +227,12 @@ def password_enabled(self): widget=forms.PasswordInput, help_text=_("Enter the same password as above, for verification.")) + section = forms.ModelChoiceField( + required=False, + queryset=Section.objects.order_by('abbreviation'), + label=_('Section'), + ) + is_superuser = forms.BooleanField( label=_("Administrator"), required=False, help_text=_('Administrators have full access to manage any object ' @@ -304,6 +321,11 @@ def save(self, commit=True): class UserEditForm(UserForm): password_required = False + section = forms.ModelChoiceField( + required=False, + queryset=Section.objects.order_by('abbreviation'), + label=_('Section'), + ) def __init__(self, *args, **kwargs): kwargs.pop('editing_self', False) @@ -348,7 +370,7 @@ class CustomUserEditForm(UserEditForm): ) section = forms.ModelChoiceField( required=False, - queryset=Section.objects, + queryset=Section.objects.order_by('abbreviation'), label=_('Section'), ) status = forms.ChoiceField( @@ -412,7 +434,7 @@ class CustomUserCreationForm(UserCreationForm): ) section = forms.ModelChoiceField( required=False, - queryset=Section.objects, + queryset=Section.objects.order_by('abbreviation'), label=_("Section"), ) From d76e50adff40e61cb05fc237bec8b99c64a01d8e Mon Sep 17 00:00:00 2001 From: Hampus Toft <136370606+hato1883@users.noreply.github.com> Date: Wed, 8 Nov 2023 19:59:58 +0100 Subject: [PATCH 3/7] Fixed number check. (#791) Reusing the the check that is used when creating a member on https://utn.se/accounts/register/ --- src/members/forms.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/members/forms.py b/src/members/forms.py index c6419f0d..36b437dc 100644 --- a/src/members/forms.py +++ b/src/members/forms.py @@ -351,10 +351,9 @@ class CustomUserEditForm(UserEditForm): help_text=_('Person number using the YYYYMMDD-XXXX format.'), required=False ) - phone_number = forms.CharField( - required=True, - label=_('Phone number'), - ) + + phone_number = PhoneNumberField() + email = forms.EmailField( required=True, label=_('Email'), @@ -419,10 +418,7 @@ class CustomUserCreationForm(UserCreationForm): required=True, label=_('Email'), ) - phone_number = forms.CharField( - required=True, - label=_('Phone number'), - ) + phone_number = PhoneNumberField() registration_year = forms.CharField( required=False, label=_('Registration year'), From f28a3e1dbbaf729ffe8b2f218c1f5616a5545ba4 Mon Sep 17 00:00:00 2001 From: Hampus Toft <136370606+hato1883@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:36:36 +0100 Subject: [PATCH 4/7] Fix footer translation (#794) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed the quickfix, added translation string to po * Test changing req. * Undo * Test #2 --------- Co-authored-by: Robin Dymér --- requirements.txt | 3 ++- src/branding/locale/sv/LC_MESSAGES/django.po | 2 +- src/branding/models.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 26c27b5a..4312be0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ +django_recaptcha==3.0.0 Django[argon2]==3.2.17 wagtail==2.16.2 @@ -40,4 +41,4 @@ django-jsonschema-form==1.0.3 python-decouple==3.7 # Search and select widget -django-select2==8.1.1 +django-select2==8.1.1 \ No newline at end of file diff --git a/src/branding/locale/sv/LC_MESSAGES/django.po b/src/branding/locale/sv/LC_MESSAGES/django.po index fe9f3619..801969f3 100644 --- a/src/branding/locale/sv/LC_MESSAGES/django.po +++ b/src/branding/locale/sv/LC_MESSAGES/django.po @@ -19,7 +19,7 @@ msgstr "Branding" #: branding/models.py:15 msgid "footer" -msgstr "" +msgstr "sidfot" #: branding/models.py:33 msgid "social media accounts" diff --git a/src/branding/models.py b/src/branding/models.py index 33820fe0..1d3a9a13 100644 --- a/src/branding/models.py +++ b/src/branding/models.py @@ -13,7 +13,7 @@ @register_setting(icon='fa-window-minimize') class FooterSettings(BaseSetting): class Meta: - verbose_name = _('footer_en') # quickfix + verbose_name = _('footer') # quickfix footer_en = StreamField( [('column', blocks.StructBlock([ From 5fbd11a51d0075502145295d0d4cddd71b24094c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Ald=C3=A9n?= <30798446+ludvigalden@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:19:53 +0100 Subject: [PATCH 5/7] rename melos to unicore (#784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rename melos to unicore * fix formatting * Fix lint * Fix migrations * Fix build check bug --------- Co-authored-by: Robin Dymér --- .env-template | 8 +- CHANGELOG.md | 8 +- README.md | 2 +- src/events/models/participant.py | 4 +- src/events/views/ticket_page.py | 4 +- src/involvement/forms/appointment_form.py | 12 +-- src/involvement/tests.py | 2 +- src/involvement/views/view_position.py | 2 +- src/members/cron.py | 2 +- src/members/forms.py | 27 +++--- .../management/commands/createsuperuser.py | 16 ++-- .../migrations/0007_member_melos_id.py | 2 +- .../migrations/0008_auto_20191203_1447.py | 4 +- .../migrations/0009_auto_20191206_1435.py | 2 +- .../migrations/0013_auto_20201217_1503.py | 6 +- .../0015_rename_melos_id_member_unicore_id.py | 18 ++++ src/members/models/member.py | 88 ++++++++++--------- src/members/tests.py | 6 +- src/members/views.py | 6 +- src/moore/settings/base.py | 12 +-- .../{melos_client.py => unicore_client.py} | 42 ++++----- 21 files changed, 149 insertions(+), 124 deletions(-) create mode 100644 src/members/migrations/0015_rename_melos_id_member_unicore_id.py rename src/utils/{melos_client.py => unicore_client.py} (65%) diff --git a/.env-template b/.env-template index cbb5c141..e91867e6 100644 --- a/.env-template +++ b/.env-template @@ -1,7 +1,7 @@ # Make a copy of this file and rename it to .env if you are using docker. This file is only meant for development -# MELOS_URL= -# MELOS_ORG_ID= -# MELOS_ADMIN= +# UNICORE_URL= +# UNICORE_ORG_ID= +# UNICORE_ADMIN= # GOOGLE_API_KEY= @@ -14,4 +14,4 @@ # DJANGO_DB_USER= # DJANGO_DB_PASS= # DJANGO_DB_HOST= -# DJANGO_DB_PORT= \ No newline at end of file +# DJANGO_DB_PORT= diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e0928c..16a637c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -274,7 +274,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- Extended createsuperuser so it gets a melos id +- Extended createsuperuser so it gets a unicore id - Teams, roles and positions are now ordered alphabetically after their name - Updated django-compressor to 2.4 - Updated django-libsass to 0.8 @@ -326,10 +326,10 @@ Exception to applications where a board member can see and modify applications f | Applications | Full access | Board/Presidium (all teams) | Presidium (all teams) | Group Leader/Engaged | Engaged | | | Contact Cards | Full access | Board/Presidium (all teams) | Presidium | Group Leader/Engaged | Engaged | | -- Melos is now used to fetch user-information such as name, ssn and member status. -- Registering an account requires the SSN to exist in Melos. +- Unicore is now used to fetch user-information such as name, ssn and member status. +- Registering an account requires the SSN to exist in Unicore. - A user may now log in using both username or ssn. -- Firstname, Lastname, SSN, Registration Year and Study Program is no longer stored in Moores database and is instead fetched from Melos. +- Firstname, Lastname, SSN, Registration Year and Study Program is no longer stored in Moores database and is instead fetched from Unicore. - Replaced Marvin to Bocken - Upgrade dependencies diff --git a/README.md b/README.md index 6979dcff..bf2ec4ff 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To get started with Project Moore, follow these instructions to set up a - libpq-dev 4. Clone the repository. 5. Copy the file `.env-template` and name the copy `.env` -6. Fill in the necessary variables in `.env`. `MELOS_URL` and `MELOS_ADMIN` are required. You might have to fill in some database credidentils. Check `src/moore/settings/base.py` for which default values are used if you don't specify and credidentials. +6. Fill in the necessary variables in `.env`. `UNICORE_URL` and `UNICORE_ADMIN` are required. You might have to fill in some database credidentils. Check `src/moore/settings/base.py` for which default values are used if you don't specify and credidentials. 7. Run `source ./source_me.sh` to create a virtual environment. 8. Run `pip install --upgrade pip` to make sure that pip is running the latest version 9. Run `pip install -r dev-requirements.txt` diff --git a/src/events/models/participant.py b/src/events/models/participant.py index 14f2fe56..402df678 100644 --- a/src/events/models/participant.py +++ b/src/events/models/participant.py @@ -1,7 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from wagtail.admin.edit_handlers import MultiFieldPanel, FieldPanel -from utils.melos_client import MelosClient +from utils.unicore_client import UnicoreClient class Participant(models.Model): @@ -40,7 +40,7 @@ def __str__(self): def calculate_order_cost(self): cost = 0 price_list = self.ticket.event.price_list - is_member = self.person_nr and MelosClient.is_member(self.person_nr) + is_member = self.person_nr and UnicoreClient.is_member(self.person_nr) if is_member: cost += self.ticket.event.price_per_participant diff --git a/src/events/views/ticket_page.py b/src/events/views/ticket_page.py index 411d98b2..6caf38f4 100644 --- a/src/events/views/ticket_page.py +++ b/src/events/views/ticket_page.py @@ -4,7 +4,7 @@ from events.models import Event, Ticket, Participant from events.forms import ParticipantForm from django.contrib.auth.decorators import login_required -from utils.melos_client import MelosClient +from utils.unicore_client import UnicoreClient @login_required @@ -76,7 +76,7 @@ def my_ticket(request, event_pk): # Set this as the first user has to be the owner. formset[0].fields['person_nr'].disabled = True - owner_is_member = MelosClient.is_member(ticket.owner.person_nr) + owner_is_member = UnicoreClient.is_member(ticket.owner.person_nr) base_price = event.base_price if owner_is_member \ else event.base_price_nonmember cost = base_price + \ diff --git a/src/involvement/forms/appointment_form.py b/src/involvement/forms/appointment_form.py index e227334b..873ce89a 100644 --- a/src/involvement/forms/appointment_form.py +++ b/src/involvement/forms/appointment_form.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from involvement.models import Application from utils.forms import AdvancedModelMultipleChoiceField -from utils.melos_client import MelosClient +from utils.unicore_client import UnicoreClient class AppointmentForm(forms.Form): @@ -39,17 +39,17 @@ def clean_overturn(self): pnrs = string.split(',') users = [] for pnr in pnrs: - melos_id = MelosClient.get_melos_id(pnr) + unicore_id = UnicoreClient.get_unicore_id(pnr) if not get_user_model().objects.filter( - melos_id=melos_id - ).exists() or melos_id is False: + unicore_id=unicore_id + ).exists() or unicore_id is False: raise forms.ValidationError( _('No user with the person number %(pnr)s exists.'), params={'pnr': pnr}, ) elif self.position.applications.filter( - applicant__melos_id=melos_id, + applicant__unicore_id=unicore_id, ).exclude( status='draft' ).exists(): @@ -61,7 +61,7 @@ def clean_overturn(self): ) else: users.append(get_user_model().objects.filter( - melos_id=melos_id + unicore_id=unicore_id ).first()) return users diff --git a/src/involvement/tests.py b/src/involvement/tests.py index 6a941e49..d8482271 100644 --- a/src/involvement/tests.py +++ b/src/involvement/tests.py @@ -32,7 +32,7 @@ def create_test_user(): user_data['password'] = 'password' user_data[user_model.EMAIL_FIELD] = "test@email.com" user_data['phone_number'] = "0733221121" - user_data['melos_id'] = 8631280 + user_data['unicore_id'] = 8631280 user_model.objects.create_superuser(**user_data) diff --git a/src/involvement/views/view_position.py b/src/involvement/views/view_position.py index 1743aaec..95d813b6 100644 --- a/src/involvement/views/view_position.py +++ b/src/involvement/views/view_position.py @@ -19,7 +19,7 @@ def view_position(request, context, page, position=None): # Load application form if user is logged in if request.user.is_authenticated: - if request.user.melos_id: + if request.user.unicore_id: context['membership_status'] = request.user.get_status context['email'] = request.user.get_email context['phone'] = request.user.get_phone_formatted diff --git a/src/members/cron.py b/src/members/cron.py index 1cd57c6b..34dc6afa 100644 --- a/src/members/cron.py +++ b/src/members/cron.py @@ -5,7 +5,7 @@ # There is now good way for bulk updates and -# without bulk update we will need to make at least 2 requests against melos to +# without bulk update we'll need to make at least 2 requests against unicore to # update one single member. To reduce the amounts of requests, # we will only update status for active members e.g the ones that has # status="member" as this value is updated when you log in to your account. diff --git a/src/members/forms.py b/src/members/forms.py index 36b437dc..8cf07b92 100644 --- a/src/members/forms.py +++ b/src/members/forms.py @@ -17,7 +17,7 @@ from django.utils.http import urlsafe_base64_encode from members.models import StudyProgram, Member, Section -from utils.melos_client import MelosClient +from utils.unicore_client import UnicoreClient from members.fields import PhoneNumberField, PersonNumberField User = get_user_model() @@ -66,11 +66,11 @@ def clean_username(self): def clean_person_number(self): person_number = self.cleaned_data['person_number'] if self.instance.pk is None: - melos_id = MelosClient.get_melos_id(person_number) - if not melos_id or Member.find_by_melos_id(melos_id): + unicore_id = UnicoreClient.get_unicore_id(person_number) + if not unicore_id or Member.find_by_unicore_id(unicore_id): raise forms.ValidationError(_("Incorrect SSN")) - self.instance.melos_id = melos_id + self.instance.unicore_id = unicore_id return person_number @@ -98,13 +98,16 @@ class Meta: field_classes = {'username': auth.UsernameField} def save(self): - melos_id = MelosClient.get_melos_id(self.cleaned_data['person_number']) + unicore_id = UnicoreClient.get_unicore_id( + self.cleaned_data['person_number'] + ) + return Member.objects.create_user( self.cleaned_data['username'], self.cleaned_data['password1'], self.cleaned_data['email'], self.cleaned_data['phone_number'], - melos_id, + unicore_id, section=self.cleaned_data['section'] ) @@ -116,8 +119,8 @@ class CustomPasswordResetForm(forms.Form): help_text=_('Person number using the YYYYMMDD-XXXX format.'), ) - def get_email(self, melos_id): - member = Member.find_by_melos_id(melos_id) + def get_email(self, unicore_id): + member = Member.find_by_unicore_id(unicore_id) if member: return member.get_email return '' @@ -447,11 +450,11 @@ def __init__(self, *args, **kwargs): def clean_person_number(self): person_number = self.cleaned_data['person_number'] - melos_id = MelosClient.get_melos_id(person_number) - if not melos_id or Member.find_by_melos_id(melos_id): + unicore_id = UnicoreClient.get_unicore_id(person_number) + if not unicore_id or Member.find_by_unicore_id(unicore_id): raise forms.ValidationError(_("Incorrect SSN")) - self.instance.melos_id = melos_id + self.instance.unicore_id = unicore_id return person_number def save(self): @@ -460,7 +463,7 @@ def save(self): "password": self.cleaned_data["password1"], "email": self.cleaned_data["email"], "phone_number": self.cleaned_data["phone_number"], - "melos_id": self.instance.melos_id, + "unicore_id": self.instance.unicore_id, 'study': self.cleaned_data['study'], "section": self.cleaned_data["section"], "registration_year": self.cleaned_data["registration_year"] diff --git a/src/members/management/commands/createsuperuser.py b/src/members/management/commands/createsuperuser.py index 95a1d971..eb811ed2 100644 --- a/src/members/management/commands/createsuperuser.py +++ b/src/members/management/commands/createsuperuser.py @@ -7,22 +7,22 @@ class Command(createsuperuser.Command): def get_input_data(self, field, message, default=None): """ Extends get_input_data from the build in createsuperuser - so that we can get a melos id for the superuser to be + so that we can get a unicore id for the superuser to be created. """ - if field.name == "melos_id": - melos_id = None - while melos_id is None: + if field.name == "unicore_id": + unicore_id = None + while unicore_id is None: ssn = input("Personnummer: ") - found_user, found_melos_id = Member.find_by_ssn(ssn) + found_user, found_unicore_id = Member.find_by_ssn(ssn) if found_user is None: - melos_id = found_melos_id + unicore_id = found_unicore_id else: self.stderr.write( "An account with that personnummer already exists" ) - melos_id = None + unicore_id = None - return melos_id + return unicore_id else: return super().get_input_data(field, message, default) diff --git a/src/members/migrations/0007_member_melos_id.py b/src/members/migrations/0007_member_melos_id.py index f5407194..0660d013 100644 --- a/src/members/migrations/0007_member_melos_id.py +++ b/src/members/migrations/0007_member_melos_id.py @@ -47,4 +47,4 @@ class Migration(migrations.Migration): name='status', field=models.CharField(blank=True, null=True, default='', max_length=130), ), - ] + ] \ No newline at end of file diff --git a/src/members/migrations/0008_auto_20191203_1447.py b/src/members/migrations/0008_auto_20191203_1447.py index 99660686..b51bcdc8 100644 --- a/src/members/migrations/0008_auto_20191203_1447.py +++ b/src/members/migrations/0008_auto_20191203_1447.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AlterModelManagers( name='member', managers=[ - ('objects', members.models.member.MelosUserManager()), + ('objects', members.models.member.UnicoreUserManager()), ], ), migrations.AlterField( @@ -48,4 +48,4 @@ class Migration(migrations.Migration): name='status', field=models.CharField(choices=[('unknown', 'Unknown'), ('nonmember', 'Nonmember'), ('member', 'Member'), ('alumnus', 'Alumnus')], default='unknown', max_length=20, verbose_name='Membership status'), ), - ] + ] \ No newline at end of file diff --git a/src/members/migrations/0009_auto_20191206_1435.py b/src/members/migrations/0009_auto_20191206_1435.py index 4c77df39..957083f7 100644 --- a/src/members/migrations/0009_auto_20191206_1435.py +++ b/src/members/migrations/0009_auto_20191206_1435.py @@ -15,4 +15,4 @@ class Migration(migrations.Migration): name='melos_id', field=models.IntegerField(blank=True, editable=False, null=True, unique=True), ), - ] + ] \ No newline at end of file diff --git a/src/members/migrations/0013_auto_20201217_1503.py b/src/members/migrations/0013_auto_20201217_1503.py index 377bdc39..c15a4758 100644 --- a/src/members/migrations/0013_auto_20201217_1503.py +++ b/src/members/migrations/0013_auto_20201217_1503.py @@ -2,7 +2,7 @@ from django.db import migrations from django.db.models import Q -from utils.melos_client import MelosClient +from utils.unicore_client import UnicoreClient # This is a copy of the function used to get the melos # data for a member. The reason it is copied is that django migrations @@ -11,7 +11,7 @@ # makes sure that this migration file will work in the future, regardless # of the changes to the Member model def fetch_and_save_melos_info(melos_id): - melos_data = MelosClient.get_user_data(melos_id) + melos_data = UnicoreClient.get_user_data(melos_id) if melos_data is not None: name = "{} {}".format( melos_data['first_name'].strip(), @@ -56,4 +56,4 @@ class Migration(migrations.Migration): get_user_info, reverse_code=migrations.RunPython.noop ) - ] + ] \ No newline at end of file diff --git a/src/members/migrations/0015_rename_melos_id_member_unicore_id.py b/src/members/migrations/0015_rename_melos_id_member_unicore_id.py new file mode 100644 index 00000000..9f7c4a7a --- /dev/null +++ b/src/members/migrations/0015_rename_melos_id_member_unicore_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.17 on 2023-11-08 18:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0014_auto_20201217_1522'), + ] + + operations = [ + migrations.RenameField( + model_name='member', + old_name='melos_id', + new_name='unicore_id', + ), + ] diff --git a/src/members/models/member.py b/src/members/models/member.py index 3a0a4292..721429e5 100644 --- a/src/members/models/member.py +++ b/src/members/models/member.py @@ -9,7 +9,7 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from simple_email_confirmation.models import SimpleEmailConfirmationUserMixin -from utils.melos_client import MelosClient +from utils.unicore_client import UnicoreClient from utils.validators import SSNValidator from phonenumbers import format_number, PhoneNumberFormat, parse @@ -26,8 +26,8 @@ def get_by_natural_key(self, username): return self.get(**{case_insensitive_username_field: username}) -class MelosUserManager(CaseInsensitiveUsernameUserManager): - # Search Member through Melos if username is SSN +class UnicoreUserManager(CaseInsensitiveUsernameUserManager): + # Search Member through Unicore if username is SSN def get_by_natural_key(self, username): member = None try: @@ -42,26 +42,26 @@ def get_by_natural_key(self, username): def _create_user( self, username, password, - email, phone_number, melos_id, + email, phone_number, unicore_id, is_superuser=False, is_staff=False, study=None, section=None, registration_year="" ): - melos_data = MelosClient.get_user_data(melos_id) + unicore_data = UnicoreClient.get_user_data(unicore_id) name = "" person_nr = "" - if melos_data is not None: + if unicore_data is not None: name = "{} {}".format( - melos_data['first_name'].strip(), - melos_data['last_name'].strip() + unicore_data['first_name'].strip(), + unicore_data['last_name'].strip() ) - person_nr = melos_data["person_number"] + person_nr = unicore_data["person_number"] user = Member.objects.create( username=username, - melos_id=melos_id, + unicore_id=unicore_id, email=email, phone_number=phone_number, is_superuser=is_superuser, @@ -80,26 +80,26 @@ def _create_user( def create_superuser( self, username, password, - email, phone_number, melos_id, + email, phone_number, unicore_id, study=None, section=None, registration_year="" ): - """Creates a new superuser with a melos id.""" + """Creates a new superuser with a unicore id.""" return self._create_user( username, password, email, - phone_number, melos_id, + phone_number, unicore_id, is_superuser=True, is_staff=True, study=study, section=section, registration_year=registration_year ) def create_user( self, username, password, - email, phone_number, melos_id, + email, phone_number, unicore_id, study=None, section=None, registration_year="" ): - """Creates a user with a melos id.""" + """Creates a user with a unicore id.""" return self._create_user( username, password, email, - phone_number, melos_id, + phone_number, unicore_id, study=study, section=section, registration_year=registration_year ) @@ -110,7 +110,7 @@ class Member( PermissionsMixin): """This class describes a member""" - objects = MelosUserManager() + objects = UnicoreUserManager() username_validator = UnicodeUsernameValidator() EMAIL_FIELD = 'email' @@ -119,7 +119,7 @@ class Member( REQUIRED_FIELDS = [ AbstractBaseUser.get_email_field_name(), "phone_number", - "melos_id" + "unicore_id" ] # ---- Necessary fields --- @@ -157,7 +157,7 @@ class Member( date_joined = models.DateTimeField(_('date joined'), default=timezone.now) - # ----- Fields for caching user information from melos + # ----- Fields for caching user information from unicore name = models.CharField( max_length=254, @@ -235,16 +235,16 @@ class Member( blank=True, ) - # ---- Melos ------ + # ---- Unicore ------ - melos_id = models.IntegerField( + unicore_id = models.IntegerField( blank=True, editable=False, null=True, unique=True, ) - melos_user_data = None + unicore_user_data = None class Meta: verbose_name = _('user') @@ -286,14 +286,14 @@ def get_status(self): def get_full_name(self): return self.name - def fetch_and_save_melos_info(self): - melos_data = self.get_melos_user_data() - if melos_data is not None: + def fetch_and_save_unicore_info(self): + unicore_data = self.get_unicore_user_data() + if unicore_data is not None: self.name = "{} {}".format( - melos_data['first_name'].strip(), - melos_data['last_name'].strip() + unicore_data['first_name'].strip(), + unicore_data['last_name'].strip() ) - self.person_nr = melos_data['person_number'] + self.person_nr = unicore_data['person_number'] self.save() return True @@ -322,7 +322,7 @@ def get_ssn(self): @property def get_email(self): if not self.email: - data = self.get_melos_user_data() + data = self.get_unicore_user_data() if data | data['email']: self.email = data['email'] self.save() @@ -338,10 +338,12 @@ def update_status(self, data=None, save=True): if timezone.now() - self.status_changed < timedelta(days=1): return - melos_user_data = self.get_melos_user_data() - if melos_user_data is None: + unicore_user_data = self.get_unicore_user_data() + if unicore_user_data is None: return - is_member = MelosClient.is_member(melos_user_data['person_number']) + is_member = UnicoreClient.is_member( + unicore_user_data['person_number'] + ) data = "member" if is_member else "nonmember" if data == 'member': @@ -384,15 +386,17 @@ def sync_user_groups(self): if (group not in current_groups): group.user_set.add(self) - def get_melos_user_data(self): - if self.melos_user_data is None: - self.melos_user_data = MelosClient.get_user_data(self.melos_id) - return self.melos_user_data + def get_unicore_user_data(self): + if self.unicore_user_data is None: + self.unicore_user_data = UnicoreClient.get_user_data( + self.unicore_id + ) + return self.unicore_user_data @staticmethod - def find_by_melos_id(melos_id): - if melos_id: - return Member.objects.filter(melos_id=int(melos_id)).first() + def find_by_unicore_id(unicore_id): + if unicore_id: + return Member.objects.filter(unicore_id=int(unicore_id)).first() return None @staticmethod @@ -403,10 +407,10 @@ def find_by_ssn(ssn): user = Member.objects.filter(person_nr=ssn).first() if user is None: - melos_id = MelosClient.get_melos_id(ssn) - return Member.find_by_melos_id(melos_id), melos_id + unicore_id = UnicoreClient.get_unicore_id(ssn) + return Member.find_by_unicore_id(unicore_id), unicore_id else: - return user, user.melos_id + return user, user.unicore_id except Exception: pass diff --git a/src/members/tests.py b/src/members/tests.py index 1f08d6bb..144c8d5f 100644 --- a/src/members/tests.py +++ b/src/members/tests.py @@ -83,7 +83,7 @@ def setUp(self): password='Intel1968', email='g.moore@localhost', phone_number="0733221111", - melos_id='123456789', + unicore_id='123456789', study=self.study, registration_year='1946', ) @@ -141,9 +141,9 @@ def test_phone_format(self): formatted_phone = self.member.get_phone_formatted self.assertEqual(formatted_phone, "+44 20 8366 1177") - def test_update_melos_info(self): + def test_update_unicore_info(self): """ - Test if a user can update their information from melos + Test if a user can update their information from unicore """ # Some wrong data self.member.name = "Not myname" diff --git a/src/members/views.py b/src/members/views.py index 49a6471c..d0e82e60 100644 --- a/src/members/views.py +++ b/src/members/views.py @@ -15,7 +15,7 @@ from rest_framework.decorators import api_view from rest_framework.response import Response from members.serializers import MemberCheckSerializer -from utils.melos_client import MelosClient +from utils.unicore_client import UnicoreClient from rest_framework import status @@ -32,7 +32,7 @@ def form_valid(self, form): def post(self, request, *args, **kwargs): update_member_info = "update_member_info" in request.POST if update_member_info: - request.user.fetch_and_save_melos_info() + request.user.fetch_and_save_unicore_info() messages.add_message( self.request, messages.SUCCESS, @@ -122,7 +122,7 @@ def member_check_api(request): if serializer.is_valid(): ssn = serializer.data.get('ssn') - is_member = MelosClient.is_member(ssn) + is_member = UnicoreClient.is_member(ssn) data = {"is_member": is_member} else: error = serializer.errors.get("ssn") diff --git a/src/moore/settings/base.py b/src/moore/settings/base.py index d60eb320..b460f1f8 100644 --- a/src/moore/settings/base.py +++ b/src/moore/settings/base.py @@ -247,20 +247,20 @@ INSTAGRAM_REDIRECT_URL = config('INSTAGRAM_REDIRECT_URL', default='') try: - MELOS_URL = config('MELOS_URL') - MELOS_ADMIN = config('MELOS_ADMIN') + UNICORE_URL = config('UNICORE_URL') + UNICORE_ADMIN = config('UNICORE_ADMIN') except UndefinedValueError: - # This allows the tests to be runned without having to have MELOS_URL and - # MELOS_ADMIN since they don't use the MELOS API. But this also raises + # This allows the tests to be runned without having to have UNICORE_URL and + # UNICORE_ADMIN since they don't use the UNICORE API. But this also raises # the error if for example a developer tries to start the server but has # not filled in the variables in their .env. I.e. The variables are still # required, except for when the tests are runned. if not IS_RUNNING_TEST: raise UndefinedValueError( - "You must add MELOS_URL and MELOS_ADMIN to you .env file" + "You must add UNICORE_URL and UNICORE_ADMIN to you .env file" ) -MELOS_ORG_ID = config('MELOS_ORG_ID', default='') +UNICORE_ORG_ID = config('UNICORE_ORG_ID', default='') # Google API GOOGLE_API_KEY = config('GOOGLE_API_KEY', default='') diff --git a/src/utils/melos_client.py b/src/utils/unicore_client.py similarity index 65% rename from src/utils/melos_client.py rename to src/utils/unicore_client.py index 61cb89af..a52b5f69 100644 --- a/src/utils/melos_client.py +++ b/src/utils/unicore_client.py @@ -5,7 +5,7 @@ class MockClient: - def get_user_data(self, melos_id): + def get_user_data(self, unicore_id): return { 'first_name': 'Firstname', 'last_name': 'Lastname', @@ -18,7 +18,7 @@ def is_member(self, ssn): else: return True - def get_melos_id(self, ssn): + def get_unicore_id(self, ssn): return 100000 @@ -26,21 +26,21 @@ class ApiClient: def request_get(self, path, params=None): if params is not None: - params['orgId'] = settings.MELOS_ORG_ID + params['orgId'] = settings.UNICORE_ORG_ID return requests.get( - settings.MELOS_URL + "/" + path, - auth=HTTPBasicAuth('admin', settings.MELOS_ADMIN), + settings.UNICORE_URL + "/" + path, + auth=HTTPBasicAuth('admin', settings.UNICORE_ADMIN), params=params, ) - def get_user_data(self, melos_id): - r = self.request_get('user-by-id' + '/' + str(melos_id)) + def get_user_data(self, unicore_id): + r = self.request_get('user-by-id' + '/' + str(unicore_id)) if r.status_code == 200: response_json = r.json() # person_nr is None for exchange students with T-numbers. - # This is becuase Melos stores their personnummer + # This is becuase Unicore stores their personnummer # in medlemsnr instead of person_number if response_json['Personnr'] is None: response_json['Personnr'] = response_json["Medlemsnr"] @@ -57,7 +57,7 @@ def is_member(self, ssn): else: return False - def get_melos_id(self, ssn): + def get_unicore_id(self, ssn): parsed_ssn = ssn if (not isinstance(parsed_ssn, str)): @@ -71,28 +71,28 @@ def get_melos_id(self, ssn): return False -class MelosClient: +class UnicoreClient: client = None @staticmethod def __setup(): - if MelosClient.client is None: + if UnicoreClient.client is None: if settings.IS_RUNNING_TEST: - MelosClient.client = MockClient() + UnicoreClient.client = MockClient() else: - MelosClient.client = ApiClient() + UnicoreClient.client = ApiClient() @staticmethod - def get_user_data(melos_id): - MelosClient.__setup() - return MelosClient.client.get_user_data(melos_id) + def get_user_data(unicore_id): + UnicoreClient.__setup() + return UnicoreClient.client.get_user_data(unicore_id) @staticmethod def is_member(ssn): - MelosClient.__setup() - return MelosClient.client.is_member(ssn) + UnicoreClient.__setup() + return UnicoreClient.client.is_member(ssn) @staticmethod - def get_melos_id(ssn): - MelosClient.__setup() - return MelosClient.client.get_melos_id(ssn) + def get_unicore_id(ssn): + UnicoreClient.__setup() + return UnicoreClient.client.get_unicore_id(ssn) From fdadf3b1ad52d1bb065e25aab5e247e7d532aaa5 Mon Sep 17 00:00:00 2001 From: Hampus Toft <136370606+hato1883@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:38:58 +0100 Subject: [PATCH 6/7] Forced date to be handeld with swedish format (#793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Forced date to be handeld with swedish format * Fix build issue --------- Co-authored-by: Robin Dymér --- .../templates/involvement/open_positions.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/involvement/templates/involvement/open_positions.html b/src/involvement/templates/involvement/open_positions.html index b4b10147..ed149c3d 100644 --- a/src/involvement/templates/involvement/open_positions.html +++ b/src/involvement/templates/involvement/open_positions.html @@ -18,7 +18,10 @@

{% trans 'open positions'|title %}

{{ pos }}

- {% trans 'deadline'|capfirst %}: {{ pos.recruitment_end }} + {% trans 'deadline'|capfirst %}: + {% language 'sv' %} + {{ pos.recruitment_end|date:"SHORT_DATE_FORMAT" }} + {% endlanguage %}
@@ -31,7 +34,11 @@

{{ pos }}

{% trans 'apply' %}
-
{% trans 'deadline'|capfirst %}: {{ pos.recruitment_end|date:"SHORT_DATE_FORMAT" }}
+
+ {% trans 'deadline'|capfirst %}: + {% language 'sv' %} + {{ pos.recruitment_end|date:"SHORT_DATE_FORMAT" }} + {% endlanguage %}
From a2d25f67251ec771701cc5814b83be70760fe0e6 Mon Sep 17 00:00:00 2001 From: Hampus Toft <136370606+hato1883@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:59:35 +0100 Subject: [PATCH 7/7] Serve utn.se/admin at admin.utn.se (#798) * A Proof of concept solution to server django admin page at admin.domain.com Can be used to show wagtail admin (probably). Uses 'django-host' to server subdomain * Changed to wagtail admin page * Fixed configs so default is used * fix login paths for admin, redirect /admin to admin subdomain * fix comments * support reversing admin paths * fix tests --------- Co-authored-by: [ludvigalden](https://github.com/ludvigalden) and [hato1883](https://github.com/hato1883) --- requirements.txt | 2 ++ src/admin/urls.py | 16 ++++++++++++++++ src/admin/views.py | 11 +++++++++++ src/moore/settings/base.py | 9 +++++++++ src/moore/settings/dev.py | 3 +++ src/moore/settings/hosts.py | 8 ++++++++ src/moore/settings/production.py | 1 + src/moore/urls.py | 27 ++++++++++++++++++++++----- src/moore/urls_utils.py | 12 ++++++++++++ 9 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 src/admin/urls.py create mode 100644 src/admin/views.py create mode 100644 src/moore/settings/hosts.py create mode 100644 src/moore/urls_utils.py diff --git a/requirements.txt b/requirements.txt index 4312be0d..bf5f308a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,8 @@ django_recaptcha==3.0.0 Django[argon2]==3.2.17 wagtail==2.16.2 +# Allows subdomains +django-hosts==6.0 # External Libraries requests==2.28.2 diff --git a/src/admin/urls.py b/src/admin/urls.py new file mode 100644 index 00000000..81ec68c7 --- /dev/null +++ b/src/admin/urls.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import, unicode_literals + +from django.conf.urls import include, url + +from wagtail.admin import urls as wagtailadmin_urls + +from moore.urls import urlpatterns as base_urlpatterns + +# Use the same `urlpatterns` as other domains as the base +urlpatterns = base_urlpatterns.copy() + +# Insert `wagtailadmin_urls` at appropriate level +urlpatterns.insert( + len(urlpatterns) - 3, + url(r'', include(wagtailadmin_urls)) +) diff --git a/src/admin/views.py b/src/admin/views.py new file mode 100644 index 00000000..94c6a408 --- /dev/null +++ b/src/admin/views.py @@ -0,0 +1,11 @@ +from django.conf import settings +from django_hosts.resolvers import reverse_host +from django.http import HttpResponseRedirect + + +def redirect_admin(request, path): + protocol = 'https' if request.is_secure() else 'http' + host = reverse_host(host='admin') + if getattr(settings, 'HOST_PORT', None): + host = f"{host}:{settings.HOST_PORT}" + return HttpResponseRedirect(f'{protocol}://{host}/{path}') diff --git a/src/moore/settings/base.py b/src/moore/settings/base.py index b460f1f8..ddc4117a 100644 --- a/src/moore/settings/base.py +++ b/src/moore/settings/base.py @@ -69,6 +69,7 @@ 'captcha', 'jsonschemaform', 'django_select2', # Custom select2 widget + 'django_hosts', # Subdomain for admin site 'django.contrib.admin', # Used for wagtail admin filters 'django.contrib.auth', @@ -79,6 +80,8 @@ ] MIDDLEWARE = [ + # Subdomain for admin site. Needed by django_hosts + 'django_hosts.middleware.HostsRequestMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -89,6 +92,8 @@ 'django.middleware.locale.LocaleMiddleware', 'wagtail.contrib.redirects.middleware.RedirectMiddleware', + # Subdomain for admin site. Needed by django_hosts + 'django_hosts.middleware.HostsResponseMiddleware', ] DATABASES = { @@ -103,6 +108,10 @@ } ROOT_URLCONF = 'moore.urls' +# Needed for django hosts, enables us to publish wagtail admin on subdomain. +ROOT_HOSTCONF = 'moore.settings.hosts' +DEFAULT_HOST = 'default' + TEMPLATES = [ { diff --git a/src/moore/settings/dev.py b/src/moore/settings/dev.py index d6e2ea99..b25625df 100644 --- a/src/moore/settings/dev.py +++ b/src/moore/settings/dev.py @@ -52,6 +52,9 @@ # trailing slash BASE_URL = 'http://localhost:8000' +ALLOWED_HOSTS = ['admin.localhost', 'localhost'] +PARENT_HOST = 'localhost' +HOST_PORT = '8000' # Email # https://docs.djangoproject.com/en/1.10/ref/settings/#email-backend diff --git a/src/moore/settings/hosts.py b/src/moore/settings/hosts.py new file mode 100644 index 00000000..ff68aa72 --- /dev/null +++ b/src/moore/settings/hosts.py @@ -0,0 +1,8 @@ +from django_hosts import patterns, host +from django.conf import settings + +host_patterns = patterns( + '', + host(r'', settings.ROOT_URLCONF, name='default'), + host(r'admin', 'admin.urls', name='admin'), +) diff --git a/src/moore/settings/production.py b/src/moore/settings/production.py index c8c40ff9..bf5be084 100644 --- a/src/moore/settings/production.py +++ b/src/moore/settings/production.py @@ -40,6 +40,7 @@ BASE_URL = 'https://utn.se' ALLOWED_HOSTS = ['.utn.se', '.utnarm.se'] +PARENT_HOST = 'utn.se' # Email settings DEFAULT_FROM_EMAIL = 'info@utn.se' diff --git a/src/moore/urls.py b/src/moore/urls.py index f901be1f..bab534ba 100644 --- a/src/moore/urls.py +++ b/src/moore/urls.py @@ -2,28 +2,33 @@ from django.conf import settings from django.conf.urls import include, url -from django.urls import path +from django.urls import path, re_path from search import views as search_views -from wagtail.admin import urls as wagtailadmin_urls from wagtail.core import urls as wagtail_urls from wagtail.documents import urls as wagtaildocs_urls +from wagtail.admin import urls as wagtailadmin_urls from .api import api_router +from .urls_utils import delete_urls from members.views import member_check_api +from admin.views import redirect_admin urlpatterns = [ - # Needs to be imported before wagtail urls url(r'^api/', api_router.urls), - # Needs to be imported before wagtail admin url(r'', include('involvement.urls')), url(r'', include('events.urls')), path('member_check_api/', member_check_api, name='member_check_api'), - url(r'^admin/', include(wagtailadmin_urls)), + re_path( + r'^admin/(?P.*)$', + redirect_admin, + name='wagtailadmin_redirect' + ), + url(r'^documents/', include(wagtaildocs_urls)), url(r'^search/$', search_views.search, name='search'), @@ -35,6 +40,10 @@ path('instagram/', include('instagram.urls')), + # We need to include the `wagtailadmin_urls` to support `reverse`. + # Unless running tests, /admin/* will redirect to admin.x/*. + url(r'^admin/', include(wagtailadmin_urls)), + # For anything not caught by a more specific rule above, hand over to # Wagtail's page serving mechanism. This should be the last pattern in # the list: @@ -51,3 +60,11 @@ settings.MEDIA_URL, document_root=settings.MEDIA_ROOT ) + +# We remove the /admin redirect +# if running tests in order to make writing tests easier. +if settings.IS_RUNNING_TEST: + urlpatterns = delete_urls( + urlpatterns, + delete_name='wagtailadmin_redirect' + ) diff --git a/src/moore/urls_utils.py b/src/moore/urls_utils.py new file mode 100644 index 00000000..29ad482a --- /dev/null +++ b/src/moore/urls_utils.py @@ -0,0 +1,12 @@ +from __future__ import absolute_import, unicode_literals + + +def delete_urls(urlpatterns: list, delete_name: str): + for index, pattern in enumerate(urlpatterns): + if hasattr(pattern, 'name'): + if pattern.name == delete_name: + # Insert before index + urlpatterns.pop(index) + break + + return urlpatterns