From a7a27e1c45cbf96d11295c5f2f03ffd756a7c966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sun, 27 Aug 2023 16:09:04 +0200 Subject: [PATCH 01/13] feat(buddy-system): international index tweaks --- .../buddy_system/dashboard_block.html | 25 ++-- .../buddy_system/index_international.html | 115 +++++++++++++++--- fiesta/apps/buddy_system/urls.py | 5 +- fiesta/apps/buddy_system/views/index.py | 6 +- fiesta/apps/buddy_system/views/matching.py | 34 +++++- fiesta/apps/files/views.py | 2 +- 6 files changed, 151 insertions(+), 36 deletions(-) diff --git a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html index 546857d0..f6e53044 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html @@ -20,26 +20,35 @@ {% elif br.state == br.State.MATCHED %} {% get_user_picture br.matched_by as buddy_picture %} +
+ {% trans "find out about your buddy" %} - {% if buddy_picture %} -
+ {% if buddy_picture %}
-
- + Matched buddy picture
-
- {% endif %} + {% endif %} +
+
✅ Matched
-
It's a match!
+
+ It's a match! +
+ You have been matched with {{ br.matched_by }}. +
{% endif %} {% else %} {% get_waiting_requests_to_match as waiting_brs %}
- {% with waiting_brs.count as count %}{{ count }} waiting request{{ count|pluralize:"s" }}{% endwith %} + {% with waiting_brs.count as count %} + {{ count }} waiting request + {{ count|pluralize:"s" }} + {% endwith %}
-

My requests

- {# TODO: ugly #} - - {% for request in requests %} - - - - - - - {% empty %} - - - - {% endfor %} -
{{ request }}{{ request.responsible_section }}{{ request.created }}{{ request.state }}
- new buddy request -
-
+ + {% for br in requests %} +
+ +
+ {% empty %} + + + new buddy request + + + {% endfor %} {% endblock %} diff --git a/fiesta/apps/buddy_system/urls.py b/fiesta/apps/buddy_system/urls.py index 44f50b16..61202641 100644 --- a/fiesta/apps/buddy_system/urls.py +++ b/fiesta/apps/buddy_system/urls.py @@ -6,7 +6,7 @@ from .views import BuddySystemIndexView from .views.editor import BuddyRequestEditorDetailView, BuddyRequestsEditorView, QuickBuddyMatchView from .views.matches import MyBuddies -from .views.matching import MatchingRequestsView, ProfilePictureServeView, TakeBuddyRequestView +from .views.matching import IssuerPictureServeView, MatcherPictureServeView, MatchingRequestsView, TakeBuddyRequestView from .views.request import BuddySystemEntrance, NewRequestView, SignUpBeforeEntranceView, WannaBuddyView urlpatterns = [ @@ -30,5 +30,6 @@ path("detail/", BuddyRequestEditorDetailView.as_view(), name="editor-detail"), path("quick-match/", QuickBuddyMatchView.as_view(), name="quick-match"), # serve profile picture with proxy view - ProfilePictureServeView.as_url(user_profile_picture_storage, url_name="serve-issuer-profile-picture"), + IssuerPictureServeView.as_url(user_profile_picture_storage, url_name="serve-issuer-profile-picture"), + MatcherPictureServeView.as_url(user_profile_picture_storage, url_name="serve-matcher-profile-picture"), ] diff --git a/fiesta/apps/buddy_system/views/index.py b/fiesta/apps/buddy_system/views/index.py index f5514407..4eb324b2 100644 --- a/fiesta/apps/buddy_system/views/index.py +++ b/fiesta/apps/buddy_system/views/index.py @@ -2,7 +2,7 @@ from django.views.generic import TemplateView -from apps.buddy_system.models import BuddySystemConfiguration +from apps.buddy_system.models import BuddyRequest, BuddySystemConfiguration from apps.plugins.views import PluginConfigurationViewMixin from apps.sections.middleware.user_membership import HttpRequest from apps.sections.models import SectionMembership @@ -16,6 +16,10 @@ class BuddySystemIndexView( ): request: HttpRequest + extra_context = { + "RequestState": BuddyRequest.State, + } + def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) diff --git a/fiesta/apps/buddy_system/views/matching.py b/fiesta/apps/buddy_system/views/matching.py index 4785d3b4..673af6df 100644 --- a/fiesta/apps/buddy_system/views/matching.py +++ b/fiesta/apps/buddy_system/views/matching.py @@ -66,19 +66,45 @@ def post(self, request, pk: uuid.UUID): return HttpResponseClientRedirect("/") -class ProfilePictureServeView( +class IssuerPictureServeView( PluginConfigurationViewMixin[BuddySystemConfiguration], NamespacedFilesServeView, ): def has_permission(self, request: HttpRequest, name: str) -> bool: # is the file in requests, for whose is the related section responsible? related_requests = request.membership.section.buddy_system_requests.filter( - Q(issuer__profile__picture=name) | Q(matched_by__profile__picture=name) + issuer__profile__picture=name, ) # does have the section enabled picture displaying? return (related_requests.exists() and self.configuration and self.configuration.display_issuer_picture) or ( related_requests.filter( - Q(matched_by=request.user) | Q(issuer=request.user), state=BuddyRequest.State.MATCHED - ).exists() + state=BuddyRequest.State.MATCHED, + ) + .filter( + Q(matched_by=request.user) | Q(issuer=request.user), + ) + .exists() + ) + + +class MatcherPictureServeView( + PluginConfigurationViewMixin[BuddySystemConfiguration], + NamespacedFilesServeView, +): + def has_permission(self, request: HttpRequest, name: str) -> bool: + # is the file in requests, for whose is the related section responsible? + related_requests = request.membership.section.buddy_system_requests.filter( + matched_by__profile__picture=name, + ) + + # does have the section enabled picture displaying? + return ( + related_requests.filter( + state=BuddyRequest.State.MATCHED, + ) + .filter( + Q(matched_by=request.user) | Q(issuer=request.user), + ) + .exists() ) diff --git a/fiesta/apps/files/views.py b/fiesta/apps/files/views.py index b0009103..5426c403 100644 --- a/fiesta/apps/files/views.py +++ b/fiesta/apps/files/views.py @@ -32,7 +32,7 @@ def has_permission(self, request: HttpRequest, name: str) -> bool: @classmethod def as_url(cls, storage: NamespacedFilesStorage, url_name: str = None) -> RoutePattern: return path( - f"serve/{storage.namespace}/", + f"serve/{storage.namespace}/{url_name or 'default'}/", cls.as_view( storage=storage, ), From 4eb60b56095185f8e3106f9f749b4bf63cf6dc18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sun, 10 Sep 2023 21:10:25 +0200 Subject: [PATCH 02/13] feat(buddy-system): issuer_note --- .../0017_alter_userprofile_instagram.py | 19 ++++++++++ fiesta/apps/buddy_system/forms.py | 12 +++--- ...me_description_buddyrequest_issuer_note.py | 18 +++++++++ .../buddy_system/index_international.html | 2 +- .../fiestarequests/models/configuration.py | 6 +++ fiesta/apps/fiestarequests/models/request.py | 16 ++++++-- .../migrations/0014_alter_plugin_app_label.py | 18 +++++++++ ...sconfiguration_required_gender_and_more.py | 38 +++++++++++++++++++ 8 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 fiesta/apps/accounts/migrations/0017_alter_userprofile_instagram.py create mode 100644 fiesta/apps/buddy_system/migrations/0020_rename_description_buddyrequest_issuer_note.py create mode 100644 fiesta/apps/plugins/migrations/0014_alter_plugin_app_label.py create mode 100644 fiesta/apps/sections/migrations/0015_alter_sectionsconfiguration_required_gender_and_more.py diff --git a/fiesta/apps/accounts/migrations/0017_alter_userprofile_instagram.py b/fiesta/apps/accounts/migrations/0017_alter_userprofile_instagram.py new file mode 100644 index 00000000..594c9ef8 --- /dev/null +++ b/fiesta/apps/accounts/migrations/0017_alter_userprofile_instagram.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.4 on 2023-09-02 12:04 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0016_userprofile_facebook_userprofile_instagram_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='instagram', + field=models.CharField(blank=True, validators=[django.core.validators.RegexValidator('^[\\w\\-_.]+$')], verbose_name='instagram username'), + ), + ] diff --git a/fiesta/apps/buddy_system/forms.py b/fiesta/apps/buddy_system/forms.py index e63be5ae..c0080dd1 100644 --- a/fiesta/apps/buddy_system/forms.py +++ b/fiesta/apps/buddy_system/forms.py @@ -29,7 +29,7 @@ class NewBuddyRequestForm(BaseModelForm): class Meta: model = BuddyRequest fields = ( - "description", + "issuer_note", "interests", "responsible_section", "issuer", @@ -42,12 +42,12 @@ class Meta: "issuer": HiddenInput, } labels = { - "description": _("Tell us about yourself"), + "issuer_note": _("Tell us about yourself"), "interests": _("What are you into?"), } help_texts = { - "description": lazy( - lambda: render_to_string("buddy_system/parts/buddy_request_description_help.html"), + "issuer_note": lazy( + lambda: render_to_string("buddy_system/parts/buddy_request_issuer_note_help.html"), str, ) } @@ -67,7 +67,7 @@ def __init__(self, *args, **kwargs): if self.instance.state != BuddyRequest.State.CREATED: self.fields["matched_by"].disabled = True self.fields["matched_at"].disabled = True - self.fields["description"].disabled = True + self.fields["issuer_note"].disabled = True self.fields["interests"].disabled = True class Meta: @@ -75,7 +75,7 @@ class Meta: fields = ( "issuer", "state", - "description", + "issuer_note", "interests", "matched_by", "matched_at", diff --git a/fiesta/apps/buddy_system/migrations/0020_rename_description_buddyrequest_issuer_note.py b/fiesta/apps/buddy_system/migrations/0020_rename_description_buddyrequest_issuer_note.py new file mode 100644 index 00000000..3088706c --- /dev/null +++ b/fiesta/apps/buddy_system/migrations/0020_rename_description_buddyrequest_issuer_note.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.4 on 2023-09-02 12:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('buddy_system', '0019_alter_buddyrequest_options'), + ] + + operations = [ + migrations.RenameField( + model_name='buddyrequest', + old_name='description', + new_name='issuer_note', + ), + ] diff --git a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html index a2cc5107..3200a51b 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html @@ -42,7 +42,7 @@

{% endif %} -
{{ br.description }}
+
{{ br.issuer_note }}
diff --git a/fiesta/apps/fiestarequests/models/configuration.py b/fiesta/apps/fiestarequests/models/configuration.py index ea070a06..72842fa4 100644 --- a/fiesta/apps/fiestarequests/models/configuration.py +++ b/fiesta/apps/fiestarequests/models/configuration.py @@ -1,6 +1,7 @@ from __future__ import annotations from django.db import models +from django.utils.translation import gettext_lazy as _ from apps.fiestarequests.matching_policy import MatchingPoliciesRegister from apps.plugins.models import BasePluginConfiguration @@ -23,6 +24,11 @@ class BaseRequestSystemConfiguration(BasePluginConfiguration): help_text=MatchingPoliciesRegister.DESCRIPTION, ) + enable_note_from_matcher = models.BooleanField( + default=True, + help_text=_("Allows matcher to reply with custom notes to the request issuer"), + ) + @property def matching_policy_instance(self): # TODO: pass configuration? diff --git a/fiesta/apps/fiestarequests/models/request.py b/fiesta/apps/fiestarequests/models/request.py index 0ace4ac7..60ac1d35 100644 --- a/fiesta/apps/fiestarequests/models/request.py +++ b/fiesta/apps/fiestarequests/models/request.py @@ -33,6 +33,10 @@ class State(TextChoices): def base_request_model_factory(related_base: str): + """ + Creates a base model for requests-like models. + """ + class BaseRequest(LifecycleModelMixin, BaseTimestampedModel): class Meta(BaseTimestampedModel.Meta): abstract = True @@ -44,8 +48,8 @@ class Meta(BaseTimestampedModel.Meta): state = models.CharField( verbose_name=_("state"), - choices=BaseRequestProtocol.State.choices, - default=BaseRequestProtocol.State.CREATED, + choices=State.choices, + default=State.CREATED, max_length=16, ) issuer = models.ForeignKey( @@ -77,8 +81,12 @@ class Meta(BaseTimestampedModel.Meta): blank=True, ) - description = models.TextField( - verbose_name=_("description"), + issuer_note = models.TextField( + verbose_name=_("text from issuer"), + ) + + matcher_note = models.TextField( + verbose_name=_("text from matcher"), ) @hook(BEFORE_SAVE, when="matched_by", was=None, is_not=None) diff --git a/fiesta/apps/plugins/migrations/0014_alter_plugin_app_label.py b/fiesta/apps/plugins/migrations/0014_alter_plugin_app_label.py new file mode 100644 index 00000000..1fed9e4f --- /dev/null +++ b/fiesta/apps/plugins/migrations/0014_alter_plugin_app_label.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.4 on 2023-09-02 12:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plugins', '0013_alter_plugin_app_label'), + ] + + operations = [ + migrations.AlterField( + model_name='plugin', + name='app_label', + field=models.CharField(choices=[('buddy_system', 'Buddy System'), ('dashboard', 'Dashboard'), ('sections', 'ESN section'), ('esncards', 'ESNcard'), ('pages', 'Pages')], help_text='Defines system application, which specific plugin represents.', max_length=256, verbose_name='app label'), + ), + ] diff --git a/fiesta/apps/sections/migrations/0015_alter_sectionsconfiguration_required_gender_and_more.py b/fiesta/apps/sections/migrations/0015_alter_sectionsconfiguration_required_gender_and_more.py new file mode 100644 index 00000000..55aff879 --- /dev/null +++ b/fiesta/apps/sections/migrations/0015_alter_sectionsconfiguration_required_gender_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.4 on 2023-09-02 12:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sections', '0014_alter_sectionmembership_role'), + ] + + operations = [ + migrations.AlterField( + model_name='sectionsconfiguration', + name='required_gender', + field=models.BooleanField(blank=True, default=None, help_text='Flag if field is needed to fill in user profile: True=field is required, False=field is optional, None=field is not available', null=True, verbose_name='required gender'), + ), + migrations.AlterField( + model_name='sectionsconfiguration', + name='required_interests', + field=models.BooleanField(blank=True, default=None, help_text='Flag if field is needed to fill in user profile: True=field is required, False=field is optional, None=field is not available', null=True, verbose_name='required interests'), + ), + migrations.AlterField( + model_name='sectionsconfiguration', + name='required_nationality', + field=models.BooleanField(blank=True, default=None, help_text='Flag if field is needed to fill in user profile: True=field is required, False=field is optional, None=field is not available', null=True, verbose_name='required nationality'), + ), + migrations.AlterField( + model_name='sectionsconfiguration', + name='required_phone_number', + field=models.BooleanField(blank=True, default=None, help_text='Flag if field is needed to fill in user profile: True=field is required, False=field is optional, None=field is not available', null=True, verbose_name='required phone number'), + ), + migrations.AlterField( + model_name='sectionsconfiguration', + name='required_picture', + field=models.BooleanField(blank=True, default=None, help_text='Flag if field is needed to fill in user profile: True=field is required, False=field is optional, None=field is not available', null=True, verbose_name='required profile picture'), + ), + ] From 0ec4069c3cd3fb23c102e91d60383ab6a28b8e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sun, 24 Sep 2023 21:17:55 +0200 Subject: [PATCH 03/13] feat(buddy-system): refactored to standalone model for request matches WIP --- fiesta/apps/accounts/models/user.py | 2 +- fiesta/apps/buddy_system/admin.py | 9 ++- fiesta/apps/buddy_system/forms.py | 42 ++++++------- ...emove_buddyrequest_issuer_note_and_more.py | 59 +++++++++++++++++++ fiesta/apps/buddy_system/models/__init__.py | 3 +- fiesta/apps/buddy_system/models/request.py | 14 ++++- .../buddy_system/dashboard_block.html | 6 +- .../buddy_system/index_international.html | 2 +- .../buddy_system/templatetags/buddy_system.py | 8 +-- fiesta/apps/buddy_system/views/editor.py | 16 +++-- fiesta/apps/buddy_system/views/matching.py | 16 ++++- fiesta/apps/fiestarequests/admin.py | 31 +++++++++- fiesta/apps/fiestarequests/matching_policy.py | 2 +- .../fiestarequests/models/managers/request.py | 9 --- fiesta/apps/fiestarequests/models/request.py | 49 ++++++++------- 15 files changed, 187 insertions(+), 81 deletions(-) create mode 100644 fiesta/apps/buddy_system/migrations/0021_remove_buddyrequest_issuer_note_and_more.py diff --git a/fiesta/apps/accounts/models/user.py b/fiesta/apps/accounts/models/user.py index b2c61d04..d617b21c 100644 --- a/fiesta/apps/accounts/models/user.py +++ b/fiesta/apps/accounts/models/user.py @@ -50,7 +50,7 @@ class Meta(AbstractUser.Meta): verbose_name_plural = _("users") # a few dynamic related models - buddy_system_matched_requests: models.QuerySet + buddy_system_request_matches: models.QuerySet profile: UserProfile diff --git a/fiesta/apps/buddy_system/admin.py b/fiesta/apps/buddy_system/admin.py index aee4903e..20f16846 100644 --- a/fiesta/apps/buddy_system/admin.py +++ b/fiesta/apps/buddy_system/admin.py @@ -2,9 +2,9 @@ from django.contrib import admin -from ..fiestarequests.admin import BaseRequestAdmin +from ..fiestarequests.admin import BaseRequestAdmin, BaseRequestMatchAdmin from ..plugins.admin import BaseChildConfigurationAdmin -from .models import BuddyRequest, BuddySystemConfiguration +from .models import BuddyRequest, BuddyRequestMatch, BuddySystemConfiguration @admin.register(BuddySystemConfiguration) @@ -15,3 +15,8 @@ class BuddySystemConfigurationAdmin(BaseChildConfigurationAdmin): @admin.register(BuddyRequest) class BuddyRequestAdmin(BaseRequestAdmin): pass + + +@admin.register(BuddyRequestMatch) +class BuddyRequestMatchAdmin(BaseRequestMatchAdmin): + pass diff --git a/fiesta/apps/buddy_system/forms.py b/fiesta/apps/buddy_system/forms.py index c0080dd1..445c407c 100644 --- a/fiesta/apps/buddy_system/forms.py +++ b/fiesta/apps/buddy_system/forms.py @@ -6,9 +6,8 @@ from django.utils.translation import gettext_lazy as _ from apps.accounts.models import UserProfile -from apps.buddy_system.models import BuddyRequest +from apps.buddy_system.models import BuddyRequest, BuddyRequestMatch from apps.fiestaforms.fields.array import ChoicedArrayField -from apps.fiestaforms.fields.datetime import DateTimeLocalField from apps.fiestaforms.forms import BaseModelForm from apps.fiestaforms.widgets.models import ActiveLocalMembersFromSectionWidget, UserWidget @@ -29,7 +28,7 @@ class NewBuddyRequestForm(BaseModelForm): class Meta: model = BuddyRequest fields = ( - "issuer_note", + "note", "interests", "responsible_section", "issuer", @@ -42,12 +41,12 @@ class Meta: "issuer": HiddenInput, } labels = { - "issuer_note": _("Tell us about yourself"), + "note": _("Tell us about yourself"), "interests": _("What are you into?"), } help_texts = { - "issuer_note": lazy( - lambda: render_to_string("buddy_system/parts/buddy_request_issuer_note_help.html"), + "note": lazy( + lambda: render_to_string("buddy_system/parts/buddy_request_note_help.html"), str, ) } @@ -65,9 +64,9 @@ def __init__(self, *args, **kwargs): self.fields["issuer"].disabled = True if self.instance.state != BuddyRequest.State.CREATED: - self.fields["matched_by"].disabled = True - self.fields["matched_at"].disabled = True - self.fields["issuer_note"].disabled = True + # self.fields["matched_by"].disabled = True + # self.fields["matched_at"].disabled = True + self.fields["note"].disabled = True self.fields["interests"].disabled = True class Meta: @@ -75,18 +74,18 @@ class Meta: fields = ( "issuer", "state", - "issuer_note", + "note", "interests", - "matched_by", - "matched_at", + # "matched_by", + # "matched_at", ) field_classes = { "interests": ChoicedArrayField, - "matched_at": DateTimeLocalField, + # "matched_at": DateTimeLocalField, } widgets = { "issuer": UserWidget, - "matched_by": ActiveLocalMembersFromSectionWidget, + # "matched_by": ActiveLocalMembersFromSectionWidget, } @@ -96,18 +95,11 @@ class QuickBuddyMatchForm(BaseModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields["issuer"].disabled = True - - if self.instance.state != BuddyRequest.State.CREATED: - self.fields["matched_by"].disabled = True + # self.fields["issuer"].disabled = True class Meta: - model = BuddyRequest - fields = ( - "issuer", - "matched_by", - ) + model = BuddyRequestMatch + fields = ("matcher",) widgets = { - "issuer": UserWidget, - "matched_by": ActiveLocalMembersFromSectionWidget, + "matcher": ActiveLocalMembersFromSectionWidget, } diff --git a/fiesta/apps/buddy_system/migrations/0021_remove_buddyrequest_issuer_note_and_more.py b/fiesta/apps/buddy_system/migrations/0021_remove_buddyrequest_issuer_note_and_more.py new file mode 100644 index 00000000..e7ec4a4e --- /dev/null +++ b/fiesta/apps/buddy_system/migrations/0021_remove_buddyrequest_issuer_note_and_more.py @@ -0,0 +1,59 @@ +# Generated by Django 4.2.4 on 2023-09-24 19:17 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django_extensions.db.fields +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('buddy_system', '0020_rename_description_buddyrequest_issuer_note'), + ] + + operations = [ + migrations.RemoveField( + model_name='buddyrequest', + name='issuer_note', + ), + migrations.RemoveField( + model_name='buddyrequest', + name='matched_at', + ), + migrations.RemoveField( + model_name='buddyrequest', + name='matched_by', + ), + migrations.AddField( + model_name='buddyrequest', + name='note', + field=models.TextField(default='', verbose_name='text from issuer'), + preserve_default=False, + ), + migrations.AddField( + model_name='buddysystemconfiguration', + name='enable_note_from_matcher', + field=models.BooleanField(default=True, help_text='Allows matcher to reply with custom notes to the request issuer'), + ), + migrations.CreateModel( + name='BuddyRequestMatch', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')), + ('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')), + ('note', models.TextField(blank=True, verbose_name='text from matcher')), + ('matcher', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='buddy_system_request_matches', to=settings.AUTH_USER_MODEL, verbose_name='matched by')), + ('request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='match', to='buddy_system.buddyrequest', verbose_name='request')), + ], + options={ + 'verbose_name': 'buddy request match', + 'verbose_name_plural': 'buddy request matches', + 'ordering': ('-created',), + 'get_latest_by': 'modified', + 'abstract': False, + }, + ), + ] diff --git a/fiesta/apps/buddy_system/models/__init__.py b/fiesta/apps/buddy_system/models/__init__.py index 6f575a1c..e05998e1 100644 --- a/fiesta/apps/buddy_system/models/__init__.py +++ b/fiesta/apps/buddy_system/models/__init__.py @@ -1,9 +1,10 @@ from __future__ import annotations from .configuration import BuddySystemConfiguration -from .request import BuddyRequest +from .request import BuddyRequest, BuddyRequestMatch __all__ = [ "BuddySystemConfiguration", "BuddyRequest", + "BuddyRequestMatch", ] diff --git a/fiesta/apps/buddy_system/models/request.py b/fiesta/apps/buddy_system/models/request.py index c76e97b0..014a9f4c 100644 --- a/fiesta/apps/buddy_system/models/request.py +++ b/fiesta/apps/buddy_system/models/request.py @@ -7,7 +7,10 @@ from apps.fiestarequests.models import base_request_model_factory from apps.utils.models.fields import ArrayFieldWithDisplayableChoices -BaseRequestForBuddySystem = base_request_model_factory(related_base="buddy_system") +BaseRequestForBuddySystem, BaseRequestMatchForBuddySystem = base_request_model_factory( + final_request_model_name="buddy_system.BuddyRequest", + related_base="buddy_system", +) class BuddyRequest(BaseRequestForBuddySystem): @@ -29,3 +32,12 @@ class Meta(BaseRequestForBuddySystem.Meta): def __str__(self): return f"Buddy Request {self.issuer}: {self.get_state_display()}" + + +class BuddyRequestMatch(BaseRequestMatchForBuddySystem): + class Meta(BaseRequestForBuddySystem.Meta): + verbose_name = _("buddy request match") + verbose_name_plural = _("buddy request matches") + + def __str__(self): + return f"Buddy Request Match {self.matcher}: {self.request}" diff --git a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html index f6e53044..0bf19511 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html @@ -39,15 +39,15 @@
It's a match!
- You have been matched with {{ br.matched_by }}. + You have been matched with {{ br.match.matcher }}.
{% endif %} {% else %} {% get_waiting_requests_to_match as waiting_brs %}
{% with waiting_brs.count as count %} - {{ count }} waiting request - {{ count|pluralize:"s" }} + {{ count }} + waiting request{{ count|pluralize:"s" }} {% endwith %}
diff --git a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html index 3200a51b..768d9625 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html @@ -42,7 +42,7 @@

{% endif %}

-
{{ br.issuer_note }}
+
{{ br.note }}
diff --git a/fiesta/apps/buddy_system/templatetags/buddy_system.py b/fiesta/apps/buddy_system/templatetags/buddy_system.py index e2668d9c..78171398 100644 --- a/fiesta/apps/buddy_system/templatetags/buddy_system.py +++ b/fiesta/apps/buddy_system/templatetags/buddy_system.py @@ -76,10 +76,10 @@ def get_matched_buddy_requests(context): request: HttpRequest = context["request"] # TODO: limit by semester / time - return request.user.buddy_system_matched_requests.filter( - responsible_section=request.membership.section, - state=BuddyRequest.State.MATCHED, - ).order_by("-matched_at") + return request.user.buddy_system_request_matches.filter( + request__responsible_section=request.membership.section, + request__state=BuddyRequest.State.MATCHED, + ).order_by("-created") @register.filter diff --git a/fiesta/apps/buddy_system/views/editor.py b/fiesta/apps/buddy_system/views/editor.py index b2750dd5..589c64cf 100644 --- a/fiesta/apps/buddy_system/views/editor.py +++ b/fiesta/apps/buddy_system/views/editor.py @@ -3,15 +3,16 @@ from django.contrib.messages.views import SuccessMessageMixin from django.contrib.postgres.search import SearchVector from django.forms import TextInput +from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ -from django.views.generic import UpdateView +from django.views.generic import CreateView, UpdateView from django_filters import CharFilter, ChoiceFilter, ModelChoiceFilter from django_tables2 import Column, TemplateColumn, tables from django_tables2.utils import Accessor from apps.buddy_system.forms import BuddyRequestEditorForm, QuickBuddyMatchForm -from apps.buddy_system.models import BuddyRequest +from apps.buddy_system.models import BuddyRequest, BuddyRequestMatch from apps.fiestaforms.views.htmx import HtmxFormMixin from apps.fiestatables.columns import ImageColumn, NaturalDatetimeColumn from apps.fiestatables.filters import BaseFilterSet, ProperDateFromToRangeFilter @@ -153,17 +154,20 @@ class QuickBuddyMatchView( SuccessMessageMixin, HtmxFormMixin, AjaxViewMixin, - UpdateView, + CreateView, ): template_name = "buddy_system/editor/quick_match.html" ajax_template_name = "buddy_system/editor/quick_match_form.html" - model = BuddyRequest + model = BuddyRequestMatch form_class = QuickBuddyMatchForm success_url = reverse_lazy("buddy_system:requests") success_message = _("Buddy request has been matched.") def form_valid(self, form): - form.instance.state = BuddyRequest.State.MATCHED - form.instance.save(update_fields=["state"]) + request = get_object_or_404(BuddyRequest, pk=self.kwargs["pk"]) + form.instance.request = request + request.state = BuddyRequest.State.MATCHED + request.save(update_fields=["state"]) + return super().form_valid(form) diff --git a/fiesta/apps/buddy_system/views/matching.py b/fiesta/apps/buddy_system/views/matching.py index 673af6df..48f659ee 100644 --- a/fiesta/apps/buddy_system/views/matching.py +++ b/fiesta/apps/buddy_system/views/matching.py @@ -9,7 +9,7 @@ from django.views.generic.detail import BaseDetailView from django_htmx.http import HttpResponseClientRedirect -from apps.buddy_system.models import BuddyRequest, BuddySystemConfiguration +from apps.buddy_system.models import BuddyRequest, BuddyRequestMatch, BuddySystemConfiguration from apps.files.views import NamespacedFilesServeView from apps.plugins.middleware.plugin import HttpRequest from apps.plugins.views import PluginConfigurationViewMixin @@ -56,11 +56,21 @@ def get_queryset(self): ) def post(self, request, pk: uuid.UUID): - BuddyRequest.objects.match_by( - request=self.get_object(), + br: BuddyRequest = self.get_object() + + match = BuddyRequestMatch( + request=br, matcher=self.request.user, + # TODO: better + note=self.request.POST.get("note"), ) + # TODO: check matcher relation to responsible section? + match.save() + + br.match = match + br.state = BuddyRequest.State.MATCHED + messages.success(request, _("Request successfully matched!")) # TODO: target URL? return HttpResponseClientRedirect("/") diff --git a/fiesta/apps/fiestarequests/admin.py b/fiesta/apps/fiestarequests/admin.py index 922dfe83..a397f115 100644 --- a/fiesta/apps/fiestarequests/admin.py +++ b/fiesta/apps/fiestarequests/admin.py @@ -5,7 +5,9 @@ class BaseRequestAdmin(ModelAdmin): - list_display = ["responsible_section", "issuer", "state", "matched_by", "matched_at", "created"] + # https://github.com/gitaarik/django-admin-relation-links + + list_display = ["responsible_section", "issuer", "state", "match", "created"] date_hierarchy = "created" @@ -15,7 +17,7 @@ class BaseRequestAdmin(ModelAdmin): "state", ] - autocomplete_fields = ["issuer", "matched_by"] + autocomplete_fields = ["issuer"] search_fields = [ "issuer__username", @@ -24,3 +26,28 @@ class BaseRequestAdmin(ModelAdmin): "issuer__first_name", "responsible_section__name", ] + + +class BaseRequestMatchAdmin(ModelAdmin): + list_display = ["matcher", "note", "created"] + + date_hierarchy = "created" + + list_filter = [ + ("request__responsible_section", admin.RelatedOnlyFieldListFilter), + ("request__responsible_section__country", admin.AllValuesFieldListFilter), + ] + + autocomplete_fields = ["matcher"] + + search_fields = [ + "request__issuer__username", + "request__issuer__email", + "request__issuer__last_name", + "request__issuer__first_name", + "matcher__username", + "matcher__email", + "matcher__last_name", + "matcher__first_name", + "request_match__responsible_section__name", + ] diff --git a/fiesta/apps/fiestarequests/matching_policy.py b/fiesta/apps/fiestarequests/matching_policy.py index d8ab8198..e4916c38 100644 --- a/fiesta/apps/fiestarequests/matching_policy.py +++ b/fiesta/apps/fiestarequests/matching_policy.py @@ -39,7 +39,7 @@ def _base_filter(cls, membership: SectionMembership) -> Q: return Q( responsible_section=membership.section, state=BuddyRequest.State.CREATED, - matched_by=None, # to be sure + match__isnull=True, # to be sure ) diff --git a/fiesta/apps/fiestarequests/models/managers/request.py b/fiesta/apps/fiestarequests/models/managers/request.py index fbfc95af..e7c544db 100644 --- a/fiesta/apps/fiestarequests/models/managers/request.py +++ b/fiesta/apps/fiestarequests/models/managers/request.py @@ -6,17 +6,8 @@ from django.db.models import Manager if typing.TYPE_CHECKING: - from apps.accounts.models import User from apps.fiestarequests.models.request import BaseRequestProtocol class BaseRequestManager(Manager): model: BaseRequestProtocol | models.Model - - def match_by(self, request: BaseRequestProtocol | models.Model, matcher: User): - request.matched_by = matcher - request.state = self.model.State.MATCHED - - # TODO: check matcher relation to responsible section? - - request.save(update_fields=["matched_by", "matched_at", "state"]) diff --git a/fiesta/apps/fiestarequests/models/request.py b/fiesta/apps/fiestarequests/models/request.py index 60ac1d35..f0afca60 100644 --- a/fiesta/apps/fiestarequests/models/request.py +++ b/fiesta/apps/fiestarequests/models/request.py @@ -5,9 +5,8 @@ from django.db import models from django.db.models import TextChoices -from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ -from django_lifecycle import BEFORE_SAVE, LifecycleModelMixin, hook +from django_lifecycle import LifecycleModelMixin from apps.accounts.models import User from apps.fiestarequests.models.managers.request import BaseRequestManager @@ -32,7 +31,10 @@ class State(TextChoices): objects: BaseRequestManager -def base_request_model_factory(related_base: str): +def base_request_model_factory( + related_base: str, + final_request_model_name: str, +): """ Creates a base model for requests-like models. """ @@ -66,31 +68,34 @@ class Meta(BaseTimestampedModel.Meta): verbose_name=_("responsible section"), db_index=True, ) - matched_by = models.ForeignKey( + + note = models.TextField( + verbose_name=_("text from issuer"), + ) + + class BaseRequestMatch(BaseTimestampedModel): + class Meta(BaseTimestampedModel.Meta): + abstract = True + ordering = ("-created",) + + request = models.OneToOneField( + final_request_model_name, + related_name="match", + on_delete=models.CASCADE, + verbose_name=_("request"), + ) + + matcher = models.ForeignKey( "accounts.User", - related_name=f"{related_base}_matched_requests", + related_name=f"{related_base}_request_matches", on_delete=models.RESTRICT, verbose_name=_("matched by"), db_index=True, - null=True, - blank=True, - ) - matched_at = models.DateTimeField( - verbose_name=_("matched at"), - null=True, - blank=True, ) - issuer_note = models.TextField( - verbose_name=_("text from issuer"), - ) - - matcher_note = models.TextField( + note = models.TextField( verbose_name=_("text from matcher"), + blank=True, ) - @hook(BEFORE_SAVE, when="matched_by", was=None, is_not=None) - def set_matched_at(self): - self.matched_at = now() - - return BaseRequest + return BaseRequest, BaseRequestMatch From c8b6be8ae975d5e1024f181cb7ac926ba7fd8d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Thu, 28 Sep 2023 11:53:18 +0200 Subject: [PATCH 04/13] fix(buddy-system): renamed request note field --- .../buddy_system/templates/buddy_system/matching_requests.html | 2 +- fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html | 2 +- fiesta/apps/utils/factories/buddy_system.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fiesta/apps/buddy_system/templates/buddy_system/matching_requests.html b/fiesta/apps/buddy_system/templates/buddy_system/matching_requests.html index 211768cd..844c5f87 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/matching_requests.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/matching_requests.html @@ -60,7 +60,7 @@ {% endif %} {#

are added by filter #} - {{ br.description|censor_description|linebreaks }} + {{ br.note|censor_description|linebreaks }} {% if br.interests %}


diff --git a/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html b/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html index 4e68d760..7c771d7a 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html @@ -25,7 +25,7 @@
{#

are added by filter #} - {{ br.description|censor_description|linebreaks }} + {{ br.note|censor_description|linebreaks }}

diff --git a/fiesta/apps/utils/factories/buddy_system.py b/fiesta/apps/utils/factories/buddy_system.py index 30e901b2..1e2eb35f 100644 --- a/fiesta/apps/utils/factories/buddy_system.py +++ b/fiesta/apps/utils/factories/buddy_system.py @@ -38,7 +38,7 @@ class Meta: ), ) - description = factory.Faker("text", max_nb_chars=600) + note = factory.Faker("text", max_nb_chars=600) class BuddyRequestWithKnownUserFactory(BuddyRequestWithUserFactory): From 45b50a4d656e81203bce127ecfd06ac295113ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Fri, 29 Sep 2023 14:20:41 +0200 Subject: [PATCH 05/13] fix(buddy-system): my-buddies added to menu, fixed matched requests window --- Makefile | 2 +- fiesta/apps/buddy_system/apps.py | 15 ++++-- .../buddy_system/dashboard_block.html | 30 +++++------ .../buddy_system/index_international.html | 32 ++++++++---- .../buddy_system/matching_requests.html | 34 +++++++++++-- .../templates/buddy_system/my_buddies.html | 30 +++++------ .../parts/requests_editor_match_btn.html | 2 +- .../buddy_system/templatetags/buddy_system.py | 9 ++-- fiesta/apps/buddy_system/views/editor.py | 50 +++++++++++-------- fiesta/apps/buddy_system/views/matches.py | 4 +- fiesta/apps/buddy_system/views/matching.py | 7 ++- .../fiestarequests/models/managers/request.py | 6 ++- fiesta/apps/fiestarequests/models/request.py | 17 +++++-- 13 files changed, 152 insertions(+), 86 deletions(-) diff --git a/Makefile b/Makefile index 9faa574f..65d0af66 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ loadlegacydata: DA_CMD = loadlegacydata ## Loads all data from legacydb run fro loadlegacydata: DCFLAGS = --profile migration loadlegacydata: da -dumpdata: DA_CMD = dumpdata --exclude auth --exclude contenttypes --exclude sessions --exclude sites --exclude admin +dumpdata: DA_CMD = dumpdata --exclude auth --exclude contenttypes --exclude sessions --exclude sites --exclude admin --natural-foreign dumpdata: da fixture ?= diff --git a/fiesta/apps/buddy_system/apps.py b/fiesta/apps/buddy_system/apps.py index 2bc8e7aa..deb901c9 100644 --- a/fiesta/apps/buddy_system/apps.py +++ b/fiesta/apps/buddy_system/apps.py @@ -30,13 +30,22 @@ class BuddySystemConfig(BasePluginAppConfig): membership_not_required_urls = ("new-request",) def as_navigation_item(self, request: HttpRequest, bound_plugin: Plugin) -> NavigationItemSpec | None: - base = super().as_navigation_item(request, bound_plugin) + base = ( + super() + .as_navigation_item(request, bound_plugin) + ._replace( + children=[ + NavigationItemSpec(title=_("My Buddies"), url=reverse("buddy_system:my-buddies")), + ], + ) + ) + if not request.membership.is_privileged: return base return base._replace( - url="", - children=[ + children=base.children + + [ NavigationItemSpec(title=_("Requests"), url=reverse("buddy_system:requests")), ], ) diff --git a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html index 0bf19511..1999714e 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html @@ -60,8 +60,8 @@
My Buddies
- {% get_matched_buddy_requests as matched_requests %} - {% if not matched_requests.exists %} + {% get_matched_buddy_requests as request_matches %} + {% if not request_matches.exists %}
So empty here :(
- {% for br in matched_requests|slice:":3" %} - {% get_user_picture br.issuer as buddy_picture %} + {% for rm in request_matches|slice:":3" %} + {% get_user_picture rm.request.issuer as buddy_picture %} {% if buddy_picture %} - + {% else %} - + {% endif %} {% endfor %} - {% if matched_requests|length > 3 %} + {% if request_matches|length > 3 %} {% endif %} diff --git a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html index 768d9625..db476693 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html @@ -46,17 +46,17 @@

- {{ br.matched_by.get_full_name }} - {% if br.matched_at %} + {% if br.match.created %} {% endif %} + {{ br.match.matcher.get_full_name }}
- {% get_user_picture br.matched_by as matcher_picture %} + {% get_user_picture br.match.matcher as matcher_picture %} {% if matcher_picture %} {% endif %}
-
response from {{ br.matched_by.first_name }}
+
{{ br.match.note }}

-

Connect with {{ br.matched_by.first_name }}

+

Connect with {{ br.match.matcher.first_name }}


-

- {{ br.matched_by }} + {{ br.match.matcher }} + + {% endif %} + {% if br.match.matcher.email %} + + ✉️ + + {{ br.match.matcher.email }} {% endif %} diff --git a/fiesta/apps/buddy_system/templates/buddy_system/matching_requests.html b/fiesta/apps/buddy_system/templates/buddy_system/matching_requests.html index 844c5f87..edfb4763 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/matching_requests.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/matching_requests.html @@ -78,10 +78,13 @@ x-ref="dialog">
+ hx-boost="true" + class="flex flex-col space-y-4" + x-data="{ note: '', noteEnabled: $el.dataset.enableNoteFromMatcher === 'true' }" + data-enable-note-from-matcher="{{ configuration.enable_note_from_matcher|lower }}"> {% csrf_token %}
-

{% blocktrans %}Buddy Request Confirmation{% endblocktrans %}

+

{% blocktrans %}Buddy Request Confirmation{% endblocktrans %}

-

+

{% blocktrans %}Are you sure you want to confirm the buddy request, acknowledging that you will be responsible for being buddy?{% endblocktrans %}

+ {% if configuration.enable_note_from_matcher %} + + +

{% translate 'What to include in message?' %}

+
    +
  • {% translate 'Through which platform you will contact him/her' %}
  • +
  • {% translate "That you're looking forward to see her/him" %}
  • +
  • {% translate "Kind words to support in their international mobility" %}
  • +
+ {% endif %} + -
diff --git a/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html b/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html index 7c771d7a..fa0e023a 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html @@ -13,42 +13,42 @@ {% block main %}
- {% for br in object_list.all %} -
+ {% for match in object_list.all %} +
- {{ br.issuer.full_name_official }} + {{ match.request.issuer.full_name_official }} - {{ br.matched_at|date }} + {{ match.created|date }}
{#

are added by filter #} - {{ br.note|censor_description|linebreaks }} + {{ match.request.note|censor_description|linebreaks }}

- {% for interest in br.get_interests_display %} + {% for interest in match.request.get_interests_display %} {{ interest }} {% endfor %}
- {% if br.issuer.profile.picture %} + {% if match.request.issuer.profile.picture %}
-
- {% if br.issuer.profile_or_none.facebook %} + {% if match.request.issuer.profile_or_none.facebook %} {% endif %} - {% if br.issuer.profile_or_none.instagram %} + {% if match.request.issuer.profile_or_none.instagram %} + href="{{ match.request.issuer.profile_or_none.instagram }}"> @ + href="mailto:{{ match.request.issuer.email }}">@ {% endif %}
diff --git a/fiesta/apps/buddy_system/templates/buddy_system/parts/requests_editor_match_btn.html b/fiesta/apps/buddy_system/templates/buddy_system/parts/requests_editor_match_btn.html index b5236d02..8d77fb37 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/parts/requests_editor_match_btn.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/parts/requests_editor_match_btn.html @@ -1,5 +1,5 @@ {% load i18n %} -{% if not record.matched_by %} +{% if not record.match %} str: def get_current_buddy_request_of_user(context): request: HttpRequest = context["request"] - return get_single_object_or_none( - request.membership.user.buddy_system_issued_requests.filter( - responsible_section=request.membership.section, - ) - ) # TODO: could be more then one? + return request.membership.user.buddy_system_issued_requests.filter( + responsible_section=request.membership.section, + ).latest("created") @register.simple_tag(takes_context=True) diff --git a/fiesta/apps/buddy_system/views/editor.py b/fiesta/apps/buddy_system/views/editor.py index 589c64cf..db1a44de 100644 --- a/fiesta/apps/buddy_system/views/editor.py +++ b/fiesta/apps/buddy_system/views/editor.py @@ -3,10 +3,9 @@ from django.contrib.messages.views import SuccessMessageMixin from django.contrib.postgres.search import SearchVector from django.forms import TextInput -from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, UpdateView +from django.views.generic import UpdateView from django_filters import CharFilter, ChoiceFilter, ModelChoiceFilter from django_tables2 import Column, TemplateColumn, tables from django_tables2.utils import Accessor @@ -15,7 +14,7 @@ from apps.buddy_system.models import BuddyRequest, BuddyRequestMatch from apps.fiestaforms.views.htmx import HtmxFormMixin from apps.fiestatables.columns import ImageColumn, NaturalDatetimeColumn -from apps.fiestatables.filters import BaseFilterSet, ProperDateFromToRangeFilter +from apps.fiestatables.filters import BaseFilterSet from apps.fiestatables.views.tables import FiestaTableView from apps.sections.middleware.section_space import HttpRequest from apps.sections.views.mixins.membership import EnsurePrivilegedUserViewMixin @@ -35,7 +34,7 @@ class RequestFilter(BaseFilterSet): widget=TextInput(attrs={"placeholder": _("Hannah, Diego, Joe...")}), ) state = ChoiceFilter(choices=BuddyRequest.State.choices) - matched_when = ProperDateFromToRangeFilter(field_name="matched_at") + # matched_when = ProperDateFromToRangeFilter(field_name="match.created_at") matched_by_faculty = ModelChoiceFilter( queryset=related_faculties, @@ -63,36 +62,37 @@ class BuddyRequestsTable(tables.Table): order_by=("issuer__last_name", "issuer__first_name", "issuer__username"), attrs={"a": {"x-data": lambda: "modal($el.href)", "x-bind": "bind"}}, linkify=("buddy_system:editor-detail", {"pk": Accessor("pk")}), - verbose_name=_("Request from"), + verbose_name=_("Issuer"), ) issuer__profile__picture = ImageColumn(verbose_name="🧑") matched_by_name = Column( - accessor="matched_by.full_name_official", + accessor="match.matcher.full_name_official", order_by=( - "matched_by__last_name", - "matched_by__first_name", - "matched_by__username", + "match__matcher__last_name", + "match__matcher__first_name", + "match__matcher__username", ), ) matched_by_email = Column( - accessor="matched_by.email", + accessor="matcher.matcher.email", visible=False, ) matched_by_picture = ImageColumn( - accessor="matched_by.profile.picture", - verbose_name=_("Buddy"), + accessor="match.matcher.profile.picture", + verbose_name=_("Matcher"), ) match_request = TemplateColumn( template_name="buddy_system/parts/requests_editor_match_btn.html", exclude_from_export=True, - order_by="matched_at", + order_by="match", ) - matched_at = NaturalDatetimeColumn() + created = NaturalDatetimeColumn() + # matched_at = NaturalDatetimeColumn(accessor="match.created_at") class Meta: model = BuddyRequest @@ -104,7 +104,8 @@ class Meta: "state", "matched_by_name", "matched_by_picture", - "matched_at", + # "matched_at", + "created", "match_request", "...", ) @@ -154,20 +155,27 @@ class QuickBuddyMatchView( SuccessMessageMixin, HtmxFormMixin, AjaxViewMixin, - CreateView, + UpdateView, ): template_name = "buddy_system/editor/quick_match.html" ajax_template_name = "buddy_system/editor/quick_match_form.html" - model = BuddyRequestMatch + model = BuddyRequest form_class = QuickBuddyMatchForm success_url = reverse_lazy("buddy_system:requests") success_message = _("Buddy request has been matched.") def form_valid(self, form): - request = get_object_or_404(BuddyRequest, pk=self.kwargs["pk"]) - form.instance.request = request - request.state = BuddyRequest.State.MATCHED - request.save(update_fields=["state"]) + br: BuddyRequest = self.get_object() + + match = BuddyRequestMatch( + request=br, + matcher=form.cleaned_data.get("matcher"), + ) + + match.save() + + br.state = BuddyRequest.State.MATCHED + br.save(update_fields=["state"]) return super().form_valid(form) diff --git a/fiesta/apps/buddy_system/views/matches.py b/fiesta/apps/buddy_system/views/matches.py index 96d3f82b..ab4d1cb2 100644 --- a/fiesta/apps/buddy_system/views/matches.py +++ b/fiesta/apps/buddy_system/views/matches.py @@ -12,4 +12,6 @@ class MyBuddies(EnsureLocalUserViewMixin, ListView): template_name = "buddy_system/my_buddies.html" def get_queryset(self): - return self.request.user.buddy_system_matched_requests.prefetch_related("issuer__profile") + return self.request.user.buddy_system_request_matches.prefetch_related( + "request__issuer__profile" + ).select_related("request", "matcher") diff --git a/fiesta/apps/buddy_system/views/matching.py b/fiesta/apps/buddy_system/views/matching.py index 48f659ee..98b2bece 100644 --- a/fiesta/apps/buddy_system/views/matching.py +++ b/fiesta/apps/buddy_system/views/matching.py @@ -4,6 +4,7 @@ from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin +from django.db import transaction from django.utils.translation import gettext as _ from django.views.generic import ListView from django.views.generic.detail import BaseDetailView @@ -55,21 +56,23 @@ def get_queryset(self): membership=self.request.membership, ) + @transaction.atomic def post(self, request, pk: uuid.UUID): br: BuddyRequest = self.get_object() match = BuddyRequestMatch( request=br, matcher=self.request.user, - # TODO: better note=self.request.POST.get("note"), ) - # TODO: check matcher relation to responsible section? + # TODO: check matcher relation to responsible section + # TODO: reset any previous match for this BR match.save() br.match = match br.state = BuddyRequest.State.MATCHED + br.save(update_fields=["state"]) messages.success(request, _("Request successfully matched!")) # TODO: target URL? diff --git a/fiesta/apps/fiestarequests/models/managers/request.py b/fiesta/apps/fiestarequests/models/managers/request.py index e7c544db..37220434 100644 --- a/fiesta/apps/fiestarequests/models/managers/request.py +++ b/fiesta/apps/fiestarequests/models/managers/request.py @@ -6,8 +6,12 @@ from django.db.models import Manager if typing.TYPE_CHECKING: - from apps.fiestarequests.models.request import BaseRequestProtocol + from apps.fiestarequests.models.request import BaseRequestMatchProtocol, BaseRequestProtocol class BaseRequestManager(Manager): model: BaseRequestProtocol | models.Model + + +class BaseRequestMatchManager(Manager): + model: BaseRequestMatchProtocol | models.Model diff --git a/fiesta/apps/fiestarequests/models/request.py b/fiesta/apps/fiestarequests/models/request.py index f0afca60..c570212d 100644 --- a/fiesta/apps/fiestarequests/models/request.py +++ b/fiesta/apps/fiestarequests/models/request.py @@ -1,6 +1,5 @@ from __future__ import annotations -import datetime import typing from django.db import models @@ -9,7 +8,7 @@ from django_lifecycle import LifecycleModelMixin from apps.accounts.models import User -from apps.fiestarequests.models.managers.request import BaseRequestManager +from apps.fiestarequests.models.managers.request import BaseRequestManager, BaseRequestMatchManager from apps.sections.models import Section from apps.utils.models import BaseTimestampedModel @@ -24,13 +23,21 @@ class State(TextChoices): state: models.CharField | State issuer: models.ForeignKey | User responsible_section: models.ForeignKey | Section - matched_by: models.ForeignKey | User - matched_at: models.DateTimeField | datetime.datetime - description: models.TextField | str + # matched_by: models.ForeignKey | User + # matched_at: models.DateTimeField | datetime.datetime + note: models.TextField | str objects: BaseRequestManager +class BaseRequestMatchProtocol(typing.Protocol): + request: models.ForeignKey | BaseRequestProtocol + matcher: models.ForeignKey | User + note: models.TextField | str + + objects: BaseRequestMatchManager + + def base_request_model_factory( related_base: str, final_request_model_name: str, From fdd774181be09c3ee50844909de828c0dcb3dc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Fri, 29 Sep 2023 14:25:19 +0200 Subject: [PATCH 06/13] fix(buddy-system): request tips reformatted --- .../parts/buddy_request_description_help.html | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/fiesta/apps/buddy_system/templates/buddy_system/parts/buddy_request_description_help.html b/fiesta/apps/buddy_system/templates/buddy_system/parts/buddy_request_description_help.html index a3c30daf..fdb8d30e 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/parts/buddy_request_description_help.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/parts/buddy_request_description_help.html @@ -3,13 +3,15 @@

Few tips to write about yourself:

  • Do you have any Erasmus dreams? Mention them! -
  • - You have one special passion? Tell us about it! -
  • - You prefer talkative and active buddy with dayily contact? Write it down. -
  • - Or you're not sure about something and just wanna a few hints? No problem! -
  • - Please, do not include any contact details, neither name nor gender -- age is fine. -
-
+ +
  • + You have one special passion? Tell us about it! +
  • +
  • You prefer talkative and active buddy with daily contact? Write it down.
  • +
  • + Or you're not sure about something and just wanna a few hints? No problem! +
  • +
  • + Please, do not include any contact details, neither name nor gender -- age is fine. + +
  • From 0813119fe1615bc6623028ba6949e908923881cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Fri, 29 Sep 2023 14:49:24 +0200 Subject: [PATCH 07/13] feat(buddy-system): added fake avatars for issuer/matcher without avatar --- .../templates/accounts/dashboard_block.html | 4 +--- .../buddy_system/dashboard_block.html | 18 ++++++++++++------ .../buddy_system/index_international.html | 9 +++++++-- .../buddy_system/templatetags/buddy_system.py | 18 ------------------ fiesta/apps/utils/templatetags/utils.py | 18 ++++++++++++++++++ fiesta/templates/fiesta/parts/user_status.html | 11 +++-------- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/fiesta/apps/accounts/templates/accounts/dashboard_block.html b/fiesta/apps/accounts/templates/accounts/dashboard_block.html index 88b49349..dd6da351 100644 --- a/fiesta/apps/accounts/templates/accounts/dashboard_block.html +++ b/fiesta/apps/accounts/templates/accounts/dashboard_block.html @@ -7,11 +7,9 @@ {% compute_profile_fullness user as fullness %} {% interpolate_to_list fullness "text-red-400" "text-orange-400" "text-blue-400" "text-lime-400" as color %}
    - {# TODO: compute completness #} - {# TODO: interpolate to color #}
    {{ fullness|multiply:100|int }}% diff --git a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html index 1999714e..6886621b 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html @@ -1,4 +1,5 @@ {% load buddy_system %} +{% load utils %} {% load i18n %} {% load user_profile %}
    @@ -21,18 +22,23 @@ {% elif br.state == br.State.MATCHED %} {% get_user_picture br.matched_by as buddy_picture %}
    - {% trans "find out about your buddy" %} + {% trans "find out about your buddy" %} - {% if buddy_picture %} -
    -
    +
    +
    + {% if buddy_picture %} Matched buddy picture -
    + {% else %} + {{ br.match.matcher.first_name|first }}{{ br.match.matcher.last_name|first }} + {% endif %}
    - {% endif %} +
    +
    ✅ Matched
    diff --git a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html index db476693..5d94080b 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html @@ -1,5 +1,6 @@ {% extends "fiesta/base.html" %} {% load user_profile %} +{% load utils %} {% load i18n %} {% load breadcrumbs %} @@ -38,7 +39,9 @@

    width="{{ issuer_picture.width }}" height="{{ issuer_picture.height }}" alt="Issuer picture" /> - {# TODO: avatar for empty #} + {% else %} + {{ br.issuer.first_name|first }}{{ br.issuer.last_name|first }} {% endif %}

    @@ -63,7 +66,9 @@

    width="{{ matcher_picture.width }}" height="{{ matcher_picture.height }}" alt="Matcher picture" /> - {# TODO: avatar for empty #} + {% else %} + {{ br.match.matcher.first_name|first }}{{ br.match.matcher.last_name|first }} {% endif %}

    diff --git a/fiesta/apps/buddy_system/templatetags/buddy_system.py b/fiesta/apps/buddy_system/templatetags/buddy_system.py index 74f95fd4..8d843684 100644 --- a/fiesta/apps/buddy_system/templatetags/buddy_system.py +++ b/fiesta/apps/buddy_system/templatetags/buddy_system.py @@ -1,6 +1,5 @@ from __future__ import annotations -import hashlib import re from django import template @@ -77,20 +76,3 @@ def get_matched_buddy_requests(context): request__responsible_section=request.membership.section, request__state=BuddyRequest.State.MATCHED, ).order_by("-created") - - -@register.filter -def get_color_by_text(name: str) -> str: - hash_object = hashlib.md5(name.encode(), usedforsecurity=False) - hash_hex = hash_object.hexdigest() - - r = int(hash_hex[0:2], 16) - g = int(hash_hex[2:4], 16) - b = int(hash_hex[4:6], 16) - - if r + g + b < 100: - r += 30 - g += 30 - b += 30 - - return f"rgb({r}, {g}, {b})" diff --git a/fiesta/apps/utils/templatetags/utils.py b/fiesta/apps/utils/templatetags/utils.py index 02269560..36a9de47 100644 --- a/fiesta/apps/utils/templatetags/utils.py +++ b/fiesta/apps/utils/templatetags/utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import datetime +import hashlib import typing from collections.abc import Reversible from operator import attrgetter @@ -58,3 +59,20 @@ def zip_(value, another): @register.filter def single_unit_timeuntil(v): return timeuntil(v, depth=1) + + +@register.filter +def get_color_by_text(name: typing.Any) -> str: + hash_object = hashlib.md5(str(name).encode(), usedforsecurity=False) + hash_hex = hash_object.hexdigest() + + r = int(hash_hex[0:2], 16) + g = int(hash_hex[2:4], 16) + b = int(hash_hex[4:6], 16) + + if r + g + b < 100: + r += 30 + g += 30 + b += 30 + + return f"rgb({r}, {g}, {b})" diff --git a/fiesta/templates/fiesta/parts/user_status.html b/fiesta/templates/fiesta/parts/user_status.html index f36437a4..493861ca 100644 --- a/fiesta/templates/fiesta/parts/user_status.html +++ b/fiesta/templates/fiesta/parts/user_status.html @@ -1,4 +1,5 @@ {% load user_profile %} +{% load utils %} {% if request.user.is_authenticated %}
    {% else %} - - - - + {{ request.user.first_name|first }}{{ request.user.last_name|first }} {% endif %} {% elif br.state == br.State.MATCHED %} - {% get_user_picture br.matched_by as buddy_picture %} + {% get_user_picture br.match.matcher as buddy_picture %}
    {% trans "find out about your buddy" %} diff --git a/fiesta/apps/buddy_system/views/editor.py b/fiesta/apps/buddy_system/views/editor.py index db1a44de..01ade719 100644 --- a/fiesta/apps/buddy_system/views/editor.py +++ b/fiesta/apps/buddy_system/views/editor.py @@ -14,7 +14,7 @@ from apps.buddy_system.models import BuddyRequest, BuddyRequestMatch from apps.fiestaforms.views.htmx import HtmxFormMixin from apps.fiestatables.columns import ImageColumn, NaturalDatetimeColumn -from apps.fiestatables.filters import BaseFilterSet +from apps.fiestatables.filters import BaseFilterSet, ProperDateFromToRangeFilter from apps.fiestatables.views.tables import FiestaTableView from apps.sections.middleware.section_space import HttpRequest from apps.sections.views.mixins.membership import EnsurePrivilegedUserViewMixin @@ -34,12 +34,14 @@ class RequestFilter(BaseFilterSet): widget=TextInput(attrs={"placeholder": _("Hannah, Diego, Joe...")}), ) state = ChoiceFilter(choices=BuddyRequest.State.choices) - # matched_when = ProperDateFromToRangeFilter(field_name="match.created_at") + matched_when = ProperDateFromToRangeFilter( + field_name="match__created", + ) - matched_by_faculty = ModelChoiceFilter( + matcher_faculty = ModelChoiceFilter( queryset=related_faculties, label=_("Faculty of matcher"), - field_name="matched_by__profile__home_faculty", + field_name="match__matcher__profile__home_faculty", ) def filter_search(self, queryset, name, value): @@ -47,8 +49,8 @@ def filter_search(self, queryset, name, value): search=SearchVector( "issuer__last_name", "issuer__first_name", - "matched_by__last_name", - "matched_by__first_name", + "match__matcher__last_name", + "match__matcher__first_name", "state", ) ).filter(search=value) @@ -67,7 +69,7 @@ class BuddyRequestsTable(tables.Table): issuer__profile__picture = ImageColumn(verbose_name="🧑") - matched_by_name = Column( + matcher_name = Column( accessor="match.matcher.full_name_official", order_by=( "match__matcher__last_name", @@ -75,12 +77,12 @@ class BuddyRequestsTable(tables.Table): "match__matcher__username", ), ) - matched_by_email = Column( - accessor="matcher.matcher.email", + matcher_email = Column( + accessor="match.matcher.email", visible=False, ) - matched_by_picture = ImageColumn( + matcher_picture = ImageColumn( accessor="match.matcher.profile.picture", verbose_name=_("Matcher"), ) @@ -91,8 +93,12 @@ class BuddyRequestsTable(tables.Table): order_by="match", ) - created = NaturalDatetimeColumn() - # matched_at = NaturalDatetimeColumn(accessor="match.created_at") + requested = NaturalDatetimeColumn(verbose_name=_("Requested"), accessor="created") + matched = NaturalDatetimeColumn( + accessor="match.created", + verbose_name=_("Matched"), + attrs={"td": {"title": None}}, # TODO: fix attrs accessor + ) class Meta: model = BuddyRequest @@ -102,10 +108,10 @@ class Meta: "issuer__full_name_official", "issuer__profile__picture", "state", - "matched_by_name", - "matched_by_picture", - # "matched_at", - "created", + "matcher_name", + "matcher_picture", + "requested", + "matched", "match_request", "...", ) diff --git a/fiesta/apps/buddy_system/views/matching.py b/fiesta/apps/buddy_system/views/matching.py index 98b2bece..035d1d17 100644 --- a/fiesta/apps/buddy_system/views/matching.py +++ b/fiesta/apps/buddy_system/views/matching.py @@ -95,7 +95,7 @@ def has_permission(self, request: HttpRequest, name: str) -> bool: state=BuddyRequest.State.MATCHED, ) .filter( - Q(matched_by=request.user) | Q(issuer=request.user), + Q(match__matcher=request.user) | Q(issuer=request.user), ) .exists() ) @@ -108,7 +108,7 @@ class MatcherPictureServeView( def has_permission(self, request: HttpRequest, name: str) -> bool: # is the file in requests, for whose is the related section responsible? related_requests = request.membership.section.buddy_system_requests.filter( - matched_by__profile__picture=name, + match__matcher__profile__picture=name, ) # does have the section enabled picture displaying? @@ -117,7 +117,7 @@ def has_permission(self, request: HttpRequest, name: str) -> bool: state=BuddyRequest.State.MATCHED, ) .filter( - Q(matched_by=request.user) | Q(issuer=request.user), + Q(match__matcher=request.user) | Q(issuer=request.user), ) .exists() ) diff --git a/fiesta/apps/fiestarequests/models/request.py b/fiesta/apps/fiestarequests/models/request.py index c570212d..0e395ba1 100644 --- a/fiesta/apps/fiestarequests/models/request.py +++ b/fiesta/apps/fiestarequests/models/request.py @@ -23,8 +23,6 @@ class State(TextChoices): state: models.CharField | State issuer: models.ForeignKey | User responsible_section: models.ForeignKey | Section - # matched_by: models.ForeignKey | User - # matched_at: models.DateTimeField | datetime.datetime note: models.TextField | str objects: BaseRequestManager diff --git a/fiesta/apps/sections/templates/sections/parts/section_stats_buddy_btn.html b/fiesta/apps/sections/templates/sections/parts/section_stats_buddy_btn.html index 20efde9a..adf45f7c 100644 --- a/fiesta/apps/sections/templates/sections/parts/section_stats_buddy_btn.html +++ b/fiesta/apps/sections/templates/sections/parts/section_stats_buddy_btn.html @@ -1,3 +1,3 @@ {% load i18n %} {% trans "show all" %} + href="{% url 'buddy_system:requests' %}?matcher_faculty={{ record.pk }}">{% trans "show all" %} diff --git a/fiesta/apps/sections/views/members.py b/fiesta/apps/sections/views/members.py index 390fae7c..9629dfa2 100644 --- a/fiesta/apps/sections/views/members.py +++ b/fiesta/apps/sections/views/members.py @@ -12,6 +12,7 @@ from django_filters import CharFilter, ChoiceFilter, ModelChoiceFilter from django_tables2 import Column, TemplateColumn +from apps.buddy_system.models import BuddyRequest from apps.buddy_system.views.editor import BuddyRequestsTable from apps.fiestaforms.views.htmx import HtmxFormMixin from apps.fiestatables.columns import ImageColumn, NaturalDatetimeColumn @@ -161,10 +162,10 @@ def get_tables(self): return [ BuddyRequestsTable( request=self.request, - data=self.object.user.buddy_system_matched_requests.all(), + data=BuddyRequest.objects.filter(match__matcher=self.object.user), exclude=( - "matched_by_name", - "matched_by_picture", + "matcher_name", + "matcher_picture", "match_request", ), ), diff --git a/fiesta/apps/sections/views/stats.py b/fiesta/apps/sections/views/stats.py index 41b01088..ea630852 100644 --- a/fiesta/apps/sections/views/stats.py +++ b/fiesta/apps/sections/views/stats.py @@ -29,7 +29,7 @@ def qs(self): qs = super().qs request_counting_qs = BuddyRequest.objects.filter( - matched_by__profile__home_faculty=OuterRef("pk"), + match__matcher__profile__home_faculty=OuterRef("pk"), ) # TODO: weird, filtering via self.filters keeps lookup_expr as exact @@ -43,7 +43,7 @@ def qs(self): return qs.annotate( matched_buddy_requests=Coalesce( Subquery( - request_counting_qs.values("matched_by__profile__home_faculty") + request_counting_qs.values("match__matcher__profile__home_faculty") .annotate(count=Count("pk")) .values("count"), output_field=models.IntegerField(), diff --git a/fiesta/apps/utils/management/commands/loadlegacydata.py b/fiesta/apps/utils/management/commands/loadlegacydata.py index 65567333..7086d8b2 100644 --- a/fiesta/apps/utils/management/commands/loadlegacydata.py +++ b/fiesta/apps/utils/management/commands/loadlegacydata.py @@ -17,7 +17,7 @@ from apps.accounts.hashers import LegacyBCryptSHA256PasswordHasher from apps.accounts.models import User, UserProfile -from apps.buddy_system.models import BuddyRequest +from apps.buddy_system.models import BuddyRequest, BuddyRequestMatch from apps.sections.models import Section, SectionMembership, SectionUniversity from apps.universities.models import Faculty, University @@ -73,17 +73,24 @@ def load_requests(*, cursor: CursorWrapper): responsible_section = Section.objects.filter(universities__abbr=issuer_university).first() - BuddyRequest.objects.update_or_create( + br, _ = BuddyRequest.objects.update_or_create( issuer=ID_TO_USER[issuer_email], responsible_section=responsible_section, defaults=dict( - description=description, - matched_by=ID_TO_USER.get(matched_by_email), - matched_at=make_aware(matched_at) if matched_at else None, + note=description, state=BuddyRequest.State.MATCHED if matched_by_email else BuddyRequest.State.CREATED, ), ) + if matched_by_email: + BuddyRequestMatch.objects.create_or_update( + request=br, + defaults=dict( + matcher=ID_TO_USER[matched_by_email], + created=make_aware(matched_at) if matched_at else None, + ), + ) + secho("Processing {i: >4}: {desc}.".format(i=i, desc=description[:32].replace("\n", " "))) From bb9ab35cb4b69907e36e8bb8f795295a315de4bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sat, 30 Sep 2023 18:44:03 +0200 Subject: [PATCH 09/13] feat(buddy-system): request state badges --- .../templates/buddy_system/index_international.html | 3 ++- fiesta/apps/buddy_system/templatetags/buddy_system.py | 9 +++++++++ webpack/tailwind.config.js | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html index 5d94080b..d57e3470 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html @@ -1,4 +1,5 @@ {% extends "fiesta/base.html" %} +{% load buddy_system %} {% load user_profile %} {% load utils %} @@ -18,7 +19,7 @@

    Your Request from {{ br.created|date }} - {{ br.get_state_display }} + {{ br.get_state_display }}

    {% if br.state == RequestState.MATCHED %} diff --git a/fiesta/apps/buddy_system/templatetags/buddy_system.py b/fiesta/apps/buddy_system/templatetags/buddy_system.py index 8d843684..a4ee2e15 100644 --- a/fiesta/apps/buddy_system/templatetags/buddy_system.py +++ b/fiesta/apps/buddy_system/templatetags/buddy_system.py @@ -76,3 +76,12 @@ def get_matched_buddy_requests(context): request__responsible_section=request.membership.section, request__state=BuddyRequest.State.MATCHED, ).order_by("-created") + + +@register.filter +def request_state_to_css_variant(state: BuddyRequest.State): + return { + BuddyRequest.State.CREATED: "info", + BuddyRequest.State.MATCHED: "success", + BuddyRequest.State.CANCELLED: "danger", + }.get(state) diff --git a/webpack/tailwind.config.js b/webpack/tailwind.config.js index 92df2a0a..45ecfff1 100644 --- a/webpack/tailwind.config.js +++ b/webpack/tailwind.config.js @@ -11,7 +11,7 @@ module.exports = { }, { // TODO: till the https://github.com/esnvutbrno/buena-fiesta/issues/188 is fixed - pattern: /btn-.+/, + pattern: /(btn-.+)|(badge-.+)/, variants: ["checked"], }, ], From 2817f7cccdcbad1c287ce852fcfd08271123fea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sat, 30 Sep 2023 22:01:59 +0200 Subject: [PATCH 10/13] feat(buddy-system): single one template for request match block --- .../buddy_system/index_international.html | 101 +---------------- .../templates/buddy_system/my_buddies.html | 76 +------------ .../parts/request_match_card.html | 104 ++++++++++++++++++ 3 files changed, 109 insertions(+), 172 deletions(-) create mode 100644 fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html diff --git a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html index d57e3470..f64f8c52 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html @@ -14,105 +14,8 @@ {% block main %} {% for br in requests %} -
    -
    -

    - Your Request from {{ br.created|date }} - - {{ br.get_state_display }} -

    - - {% if br.state == RequestState.MATCHED %} -
    -
    - {{ br.issuer.get_full_name }} - -
    -
    -
    - {% get_user_picture br.issuer as issuer_picture %} - - {% if issuer_picture %} - Issuer picture - {% else %} - {{ br.issuer.first_name|first }}{{ br.issuer.last_name|first }} - {% endif %} -
    -
    -
    {{ br.note }}
    -
    -
    -
    - {% if br.match.created %} - - {% endif %} - {{ br.match.matcher.get_full_name }} -
    -
    -
    - {% get_user_picture br.match.matcher as matcher_picture %} - - {% if matcher_picture %} - Matcher picture - {% else %} - {{ br.match.matcher.first_name|first }}{{ br.match.matcher.last_name|first }} - {% endif %} -
    -
    -
    {{ br.match.note }}
    -
    - -
    -
    -

    Connect with {{ br.match.matcher.first_name }}

    -
    -
    -
    - {% if br.match.matcher.profile.facebook %} - - - - - - {{ br.match.matcher }} - - {% endif %} - {% if br.match.matcher.email %} - - ✉️ - - {{ br.match.matcher.email }} - - {% endif %} -
    - {% endif %} -
    -
    + {% blocktranslate with created=br.created|date asvar title %}Your Request from {{ created }}{% endblocktranslate %} + {% include "buddy_system/parts/request_match_card.html" with request=br title=title connect_with=br.match.matcher %} {% empty %} diff --git a/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html b/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html index fa0e023a..bd7a5761 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html @@ -12,77 +12,7 @@ {% endblock upper_head %} {% block main %} -
    - {% for match in object_list.all %} -
    -
    -
    - {{ match.request.issuer.full_name_official }} - - {{ match.created|date }} -
    - - -
    - {#

    are added by filter #} - {{ match.request.note|censor_description|linebreaks }} -

    - -
    - {% for interest in match.request.get_interests_display %} - {{ interest }} - {% endfor %} -
    - -
    - {% if match.request.issuer.profile.picture %} -
    - - -
    - {% if match.request.issuer.profile_or_none.facebook %} - - - - - - {% endif %} - {% if match.request.issuer.profile_or_none.instagram %} - - - - - - {% endif %} - {% if br.issuer.email %} - - @ - {% endif %} -
    -
    - {% endif %} - -
    - {% endfor %} -
    - + {% for match in object_list.all %} + {% include "buddy_system/parts/request_match_card.html" with request=match.request title=match.request.issuer.full_name connect_with=match.request.issuer %} + {% endfor %} {% endblock %} diff --git a/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html b/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html new file mode 100644 index 00000000..681bcd1c --- /dev/null +++ b/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html @@ -0,0 +1,104 @@ +{% load utils %} +{% load user_profile %} +{% load buddy_system %} +
    +
    +

    + {{ title }} + + + {{ request.get_state_display }} + +

    + + {% if request.state == request.State.MATCHED %} +
    +
    + {{ request.issuer.get_full_name }} + +
    +
    +
    + {% get_user_picture request.issuer as issuer_picture %} + + {% if issuer_picture %} + Issuer picture + {% else %} + {{ request.issuer.first_name|first }}{{ request.issuer.last_name|first }} + {% endif %} +
    +
    +
    {{ request.note }}
    +
    +
    +
    + {% if request.match.created %} + + {% endif %} + {{ request.match.matcher.get_full_name }} +
    +
    +
    + {% get_user_picture request.match.matcher as matcher_picture %} + + {% if matcher_picture %} + Matcher picture + {% else %} + {{ request.match.matcher.first_name|first }}{{ request.match.matcher.last_name|first }} + {% endif %} +
    +
    +
    {{ request.match.note }}
    +
    + +
    +
    +

    Connect with {{ connect_with.first_name }}

    +
    +
    +
    + {% if connect_with.profile.facebook %} + + + + + + {{ connect_with }} + + {% endif %} + {% if connect_with.email %} + + ✉️ + + {{ connect_with.email }} + + {% endif %} +
    + {% endif %} +
    +
    From 9add59c833a16d0b9548cd30a98d23af332d0b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sun, 1 Oct 2023 13:00:46 +0200 Subject: [PATCH 11/13] feat(buddy-system): contact details improved in match block --- fiesta/apps/buddy_system/apps.py | 10 +++- .../buddy_system/dashboard_block.html | 2 +- ...help.html => buddy_request_note_help.html} | 0 .../parts/request_match_card.html | 60 +++++++++++++++++-- .../buddy_system/templatetags/buddy_system.py | 9 ++- 5 files changed, 68 insertions(+), 13 deletions(-) rename fiesta/apps/buddy_system/templates/buddy_system/parts/{buddy_request_description_help.html => buddy_request_note_help.html} (100%) diff --git a/fiesta/apps/buddy_system/apps.py b/fiesta/apps/buddy_system/apps.py index deb901c9..b307a943 100644 --- a/fiesta/apps/buddy_system/apps.py +++ b/fiesta/apps/buddy_system/apps.py @@ -34,9 +34,13 @@ def as_navigation_item(self, request: HttpRequest, bound_plugin: Plugin) -> Navi super() .as_navigation_item(request, bound_plugin) ._replace( - children=[ - NavigationItemSpec(title=_("My Buddies"), url=reverse("buddy_system:my-buddies")), - ], + children=( + [ + NavigationItemSpec(title=_("My Buddies"), url=reverse("buddy_system:my-buddies")), + ] + if request.membership.is_local + else [] + ), ) ) diff --git a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html index 139c3588..408ac6ae 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/dashboard_block.html @@ -45,7 +45,7 @@
    It's a match!
    - You have been matched with {{ br.match.matcher }}. + You have been matched with {{ br.match.matcher.full_name }}.
    {% endif %} {% else %} diff --git a/fiesta/apps/buddy_system/templates/buddy_system/parts/buddy_request_description_help.html b/fiesta/apps/buddy_system/templates/buddy_system/parts/buddy_request_note_help.html similarity index 100% rename from fiesta/apps/buddy_system/templates/buddy_system/parts/buddy_request_description_help.html rename to fiesta/apps/buddy_system/templates/buddy_system/parts/buddy_request_note_help.html diff --git a/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html b/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html index 681bcd1c..8b106527 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html @@ -6,7 +6,7 @@

    {{ title }} - + {{ request.get_state_display }}

    @@ -36,6 +36,11 @@

    {{ request.note }}
    +
    + {% for interest in request.issuer.profile.get_interests_display %} + {{ interest }} + {% endfor %} +
    @@ -70,7 +75,35 @@

    Connect with {{ connect_with.first_name }}


    -
    +
    + {% if connect_with.profile.telegram %} + + + + + Telegram + + {% endif %} + {% if connect_with.profile.whatsapp %} + + + + + WhatsApp + + {% endif %} {% if connect_with.profile.facebook %} Connect with {{ connect_with.first_name }} - {{ connect_with }} + Facebook + + {% endif %} + {% if connect_with.profile.instagram %} + + + + + Instagram {% endif %} {% if connect_with.email %} @@ -94,8 +143,7 @@

    Connect with {{ connect_with.first_name }}

    target="_blank" class="btn btn-primary inline-flex items-center gap-x-2"> ✉️ - - {{ connect_with.email }} + E-mail {% endif %}
    diff --git a/fiesta/apps/buddy_system/templatetags/buddy_system.py b/fiesta/apps/buddy_system/templatetags/buddy_system.py index a4ee2e15..494d73c2 100644 --- a/fiesta/apps/buddy_system/templatetags/buddy_system.py +++ b/fiesta/apps/buddy_system/templatetags/buddy_system.py @@ -36,9 +36,12 @@ def censor_description(description: str) -> str: def get_current_buddy_request_of_user(context): request: HttpRequest = context["request"] - return request.membership.user.buddy_system_issued_requests.filter( - responsible_section=request.membership.section, - ).latest("created") + try: + return request.membership.user.buddy_system_issued_requests.filter( + responsible_section=request.membership.section, + ).latest("created") + except BuddyRequest.DoesNotExist: + return None @register.simple_tag(takes_context=True) From 363f25e390b13580d5e42cccab5f489ab23f2688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sun, 1 Oct 2023 16:04:54 +0200 Subject: [PATCH 12/13] feat(buddy-system): display chat also for non-matched requests --- .../buddy_system/index_international.html | 2 +- .../templates/buddy_system/my_buddies.html | 2 +- .../parts/request_match_card.html | 93 +++++++++++-------- 3 files changed, 58 insertions(+), 39 deletions(-) diff --git a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html index f64f8c52..54d719de 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/index_international.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/index_international.html @@ -15,7 +15,7 @@ {% for br in requests %} {% blocktranslate with created=br.created|date asvar title %}Your Request from {{ created }}{% endblocktranslate %} - {% include "buddy_system/parts/request_match_card.html" with request=br title=title connect_with=br.match.matcher %} + {% include "buddy_system/parts/request_match_card.html" with br=br title=title connect_with=br.match.matcher %} {% empty %} diff --git a/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html b/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html index bd7a5761..fa39053f 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/my_buddies.html @@ -13,6 +13,6 @@ {% block main %} {% for match in object_list.all %} - {% include "buddy_system/parts/request_match_card.html" with request=match.request title=match.request.issuer.full_name connect_with=match.request.issuer %} + {% include "buddy_system/parts/request_match_card.html" with br=match.request title=match.request.issuer.full_name connect_with=match.request.issuer %} {% endfor %} {% endblock %} diff --git a/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html b/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html index 8b106527..1723f976 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html @@ -1,3 +1,4 @@ +{% load static %} {% load utils %} {% load user_profile %} {% load buddy_system %} @@ -6,55 +7,57 @@

    {{ title }} - - {{ request.get_state_display }} + + {{ br.get_state_display }}

    - {% if request.state == request.State.MATCHED %} -
    -
    - {{ request.issuer.get_full_name }} - -
    -
    -
    - {% get_user_picture request.issuer as issuer_picture %} +
    - {% if issuer_picture %} - Issuer picture - {% else %} - {{ request.issuer.first_name|first }}{{ request.issuer.last_name|first }} - {% endif %} -
    -
    -
    {{ request.note }}
    -
    - {% for interest in request.issuer.profile.get_interests_display %} - {{ interest }} - {% endfor %} +
    + {{ br.issuer.get_full_name }} + +
    +
    +
    + {% get_user_picture br.issuer as issuer_picture %} + + {% if issuer_picture %} + Issuer picture + {% else %} + {{ br.issuer.first_name|first }}{{ br.issuer.last_name|first }} + {% endif %}
    +
    {{ br.note }}
    +
    + {% for interest in br.issuer.profile.get_interests_display %} + {{ interest }} + {% endfor %} +
    +
    + + {% if br.state == br.State.MATCHED %}
    - {% if request.match.created %} + {% if br.match.created %} {% endif %} - {{ request.match.matcher.get_full_name }} + {{ br.match.matcher.get_full_name }}
    - {% get_user_picture request.match.matcher as matcher_picture %} + {% get_user_picture br.match.matcher as matcher_picture %} {% if matcher_picture %} alt="Matcher picture" /> {% else %} {{ request.match.matcher.first_name|first }}{{ request.match.matcher.last_name|first }} + style="background-color: {{ br.match.matcher.id|get_color_by_text }}">{{ br.match.matcher.first_name|first }}{{ br.match.matcher.last_name|first }} {% endif %}
    -
    {{ request.match.note }}
    +
    {{ br.match.note }}
    @@ -147,6 +150,22 @@

    Connect with {{ connect_with.first_name }}

    {% endif %}
    + {% elif br.state == br.State.CREATED %} +
    +
    +
    +
    🤖
    +
    +
    + {% get_waiting_buddy_requests_placed_before br as waiting_total %} + +
    ⌛ Waiting for match
    + + {% if waiting_total %} + There is {{ waiting_total }} waiting request{{ waiting_total|pluralize:"s" }} before yours. + {% endif %} +
    +
    {% endif %}
    From 021d507fb0aee6d034460c419b03b3b726765754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Sun, 1 Oct 2023 21:49:59 +0200 Subject: [PATCH 13/13] feat(buddy-system): final touches --- .../buddy_system/parts/request_match_card.html | 13 ++++++++++--- .../parts/requests_editor_match_btn.html | 16 ++++++++++------ fiesta/apps/buddy_system/views/editor.py | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html b/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html index 1723f976..6af1c4ab 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/parts/request_match_card.html @@ -1,3 +1,4 @@ +{% load i18n %} {% load static %} {% load utils %} {% load user_profile %} @@ -70,7 +71,13 @@

    {% endif %}

    -
    {{ br.match.note }}
    +
    + {% if br.match.note %} + {{ br.match.note }} + {% else %} + {% translate "We have been successfully matched!" %} + {% endif %} +
    @@ -156,10 +163,10 @@

    Connect with {{ connect_with.first_name }}

    🤖
    -
    +
    {% get_waiting_buddy_requests_placed_before br as waiting_total %} -
    ⌛ Waiting for match
    +
    ⌛ {% translate "Waiting for match" %}
    {% if waiting_total %} There is {{ waiting_total }} waiting request{{ waiting_total|pluralize:"s" }} before yours. diff --git a/fiesta/apps/buddy_system/templates/buddy_system/parts/requests_editor_match_btn.html b/fiesta/apps/buddy_system/templates/buddy_system/parts/requests_editor_match_btn.html index 8d77fb37..369f4f2b 100644 --- a/fiesta/apps/buddy_system/templates/buddy_system/parts/requests_editor_match_btn.html +++ b/fiesta/apps/buddy_system/templates/buddy_system/parts/requests_editor_match_btn.html @@ -1,7 +1,11 @@ {% load i18n %} -{% if not record.match %} - {% trans "Match" %} -{% endif %} + + {% if record.match %} + {% trans "Change buddy" %} + {% else %} + {% trans "Match" %} + {% endif %} + diff --git a/fiesta/apps/buddy_system/views/editor.py b/fiesta/apps/buddy_system/views/editor.py index 01ade719..b3bdaddd 100644 --- a/fiesta/apps/buddy_system/views/editor.py +++ b/fiesta/apps/buddy_system/views/editor.py @@ -2,6 +2,7 @@ from django.contrib.messages.views import SuccessMessageMixin from django.contrib.postgres.search import SearchVector +from django.db import transaction from django.forms import TextInput from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ @@ -171,9 +172,22 @@ class QuickBuddyMatchView( success_url = reverse_lazy("buddy_system:requests") success_message = _("Buddy request has been matched.") + def get_initial(self): + try: + return { + "matcher": self.get_object().match.matcher, + } + except BuddyRequestMatch.DoesNotExist: + return {} + + @transaction.atomic def form_valid(self, form): br: BuddyRequest = self.get_object() + if br.match: + # could be already matched by someone else + br.match.delete() + match = BuddyRequestMatch( request=br, matcher=form.cleaned_data.get("matcher"),