diff --git a/scipost_django/common/utils/__init__.py b/scipost_django/common/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..38fada4d15613d37971a24a2cb11cc2f750f86f0 --- /dev/null +++ b/scipost_django/common/utils/__init__.py @@ -0,0 +1,3 @@ +from .models import * +from .text import * +from .mail import * diff --git a/scipost_django/common/utils/mail.py b/scipost_django/common/utils/mail.py new file mode 100644 index 0000000000000000000000000000000000000000..b90c65c6e9af9199f152b81fa8b91cbd14e2ebde --- /dev/null +++ b/scipost_django/common/utils/mail.py @@ -0,0 +1,50 @@ +# MARKED FOR DEPRECATION +from django.core.mail import EmailMultiAlternatives +from common.utils.models import get_current_domain + + +class BaseMailUtil(object): + mail_sender = "no-reply@%s" % get_current_domain() + mail_sender_title = "" + + @classmethod + def load(cls, _dict, request=None): + cls._context = _dict + cls._context["request"] = request + cls._context["domain"] = get_current_domain() + for var_name in _dict: + setattr(cls, var_name, _dict[var_name]) + + def _send_mail( + cls, template_name, recipients, subject, extra_bcc=None, extra_context={} + ): + """ + Call this method from a classmethod to send emails. + The template will have context variables defined appended from the `load` method. + + Arguments: + template_name -- The .html template to use in the mail. The name be used to get the + following two templates: + `email/<template_name>.txt` (non-HTML) + `email/<template_name>.html` + recipients -- List of mailaddresses to send to mail to. + subject -- The subject of the mail. + """ + template = loader.get_template("email/%s.txt" % template_name) + html_template = loader.get_template("email/%s.html" % template_name) + cls._context.update(extra_context) + message = template.render(cls._context) + html_message = html_template.render(cls._context) + bcc_list = [cls.mail_sender] + if extra_bcc: + bcc_list += extra_bcc + email = EmailMultiAlternatives( + subject, + message, + "%s <%s>" % (cls.mail_sender_title, cls.mail_sender), + recipients, + bcc=bcc_list, + reply_to=[cls.mail_sender], + ) + email.attach_alternative(html_message, "text/html") + email.send(fail_silently=False) diff --git a/scipost_django/common/utils/models.py b/scipost_django/common/utils/models.py new file mode 100644 index 0000000000000000000000000000000000000000..6ed59b7eb893017ea3137e6d9262fd156a129712 --- /dev/null +++ b/scipost_django/common/utils/models.py @@ -0,0 +1,78 @@ +from django.contrib.sites.models import Site +from django.db.models import Field, ForeignObjectRel +from django.db.models.fields.related import RelatedField + + +def get_current_domain(): + try: + return Site.objects.get_current().domain + except: + return "fake.domain" + + +def merge(old, new): + """ + Merge two model instances, `old` and `new`, by: + - copying all the fields from `old` to `new` if they are not already set + - updating all (reverse) relations from `old` to `new` + """ + model = old.__class__ + + for field in model._meta.get_fields(): + accessor = field.name or field.get_accessor_name() + old_value = getattr(old, accessor, None) + + if isinstance(field, Field): + # If new object has a value for the field, skip it + # otherwise, set the value from the old object + if getattr(new, accessor, None) is None: + setattr(new, accessor, old_value) + elif isinstance(field, RelatedField) or isinstance(field, ForeignObjectRel): + # Handle object relations + related_object = field + manager = related_object.related_model.objects + + # Guard against missing related object field names + if not hasattr(related_object, "field"): + continue + + field_name = related_object.field.name + + if related_object.one_to_one: + # For one-to-one relations, we get the related objects from the manager + # and anull (let go) the attribute of the new object + # so that it can be attached it to the old object + # ===================== + # Equivalent to: + # new.field_name = None + # old.field_name = new + manager.filter(**{field_name: new}).update(**{field_name: None}) + manager.filter(**{field_name: old}).update(**{field_name: new}) + elif related_object.many_to_many: + # For many-to-many relations, `old_value` is a manager + # and we can add the related objects to the new object + if accessor is not None and old_value is not None: + getattr(new, accessor).add(*old_value.all()) + else: + for related_queryset in manager.filter(**{field_name: old}): + getattr(related_queryset, accessor).remove(old) + getattr(related_queryset, accessor).add(new) + elif related_object.one_to_many: + # For one-to-many relations, we get the related objects from the manager + # and update the foreign key to the new object + manager.filter(**{field_name: old}).update(**{field_name: new}) + else: + # Handle many-to-one relations by setting the attribute + # of the new object if it is not already set + if getattr(new, accessor) is None: + setattr(new, accessor, old_value) + + else: + # Handle fields by setting the attribute of the new object + # if it is not already set + if getattr(new, accessor) is None: + setattr(new, accessor, old_value) + + # Save both objects + new.save() + old.save() diff --git a/scipost_django/common/utils.py b/scipost_django/common/utils/text.py similarity index 70% rename from scipost_django/common/utils.py rename to scipost_django/common/utils/text.py index 550b3aa03426fc3a1c45696f605edf82da85222b..25265f257628f6957fd8b035b8ee7c89e95e67bd 100644 --- a/scipost_django/common/utils.py +++ b/scipost_django/common/utils/text.py @@ -4,12 +4,9 @@ __license__ = "AGPL v3" import datetime -from django.contrib.sites.models import Site -from django.core.mail import EmailMultiAlternatives from django.db.models import Q -from django.template import loader -from .constants import CHARACTER_ALTERNATIVES, CHARACTER_LATINISATIONS +from ..constants import CHARACTER_ALTERNATIVES, CHARACTER_LATINISATIONS import unicodedata @@ -170,63 +167,8 @@ def jatsify_tags(text): return jatsified -def get_current_domain(): - try: - return Site.objects.get_current().domain - except: - return "fake.domain" - - def remove_extra_spacing(text): """ Remove extra spacing from text in the form of multiple spaces. """ return " ".join(text.strip().split()) - - -# MARKED FOR DEPRECATION -class BaseMailUtil(object): - mail_sender = "no-reply@%s" % get_current_domain() - mail_sender_title = "" - - @classmethod - def load(cls, _dict, request=None): - cls._context = _dict - cls._context["request"] = request - cls._context["domain"] = get_current_domain() - for var_name in _dict: - setattr(cls, var_name, _dict[var_name]) - - def _send_mail( - cls, template_name, recipients, subject, extra_bcc=None, extra_context={} - ): - """ - Call this method from a classmethod to send emails. - The template will have context variables defined appended from the `load` method. - - Arguments: - template_name -- The .html template to use in the mail. The name be used to get the - following two templates: - `email/<template_name>.txt` (non-HTML) - `email/<template_name>.html` - recipients -- List of mailaddresses to send to mail to. - subject -- The subject of the mail. - """ - template = loader.get_template("email/%s.txt" % template_name) - html_template = loader.get_template("email/%s.html" % template_name) - cls._context.update(extra_context) - message = template.render(cls._context) - html_message = html_template.render(cls._context) - bcc_list = [cls.mail_sender] - if extra_bcc: - bcc_list += extra_bcc - email = EmailMultiAlternatives( - subject, - message, - "%s <%s>" % (cls.mail_sender_title, cls.mail_sender), - recipients, - bcc=bcc_list, - reply_to=[cls.mail_sender], - ) - email.attach_alternative(html_message, "text/html") - email.send(fail_silently=False)