diff --git a/config/api_router.py b/config/api_router.py index 3e34335..ec99a8f 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -16,7 +16,6 @@ ContentShareViewSet, ContentViewViewSet, ) -from mycarehub.users.api.views import UserAPIView if settings.DEBUG: router = DefaultRouter() @@ -33,12 +32,6 @@ app_name = "api" urlpatterns = router.urls + [ - path("users//", UserAPIView.as_view(), name="users-detail"), - path( - "users/", - UserAPIView.as_view(), - name="users-general", - ), path("organisations//", OrganisationAPIView.as_view(), name="organisations-detail"), path( "organisations/", diff --git a/config/settings/base.py b/config/settings/base.py index 811b9aa..2fe8e87 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -93,8 +93,7 @@ "wagtail.images", "wagtail.search", "wagtail.admin", - "wagtail.core", - "wagtail.contrib.modeladmin", + "wagtail", "wagtailmedia", "taggit", "modelcluster", @@ -104,8 +103,6 @@ "wagtail.contrib.search_promotions", "wagtail.contrib.simple_translation", "wagtail.locales", - "wagtailreadinglevel", - "wagtailfontawesome", "django_extensions", "nested_admin", ] @@ -164,6 +161,7 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.common.BrokenLinkEmailsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "allauth.account.middleware.AccountMiddleware", ] # STATIC @@ -189,11 +187,8 @@ "DIRS": [ str(APPS_DIR / "templates"), ], + "APP_DIRS": True, "OPTIONS": { - "loaders": [ - "django.template.loaders.filesystem.Loader", - "django.template.loaders.app_directories.Loader", - ], "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", @@ -296,6 +291,7 @@ ACCOUNT_UNIQUE_EMAIL = True ACCOUNT_USERNAME_MIN_LENGTH = 5 ACCOUNT_DEFAULT_HTTP_PROTOCOL = env.str("DJANGO_ACCOUNT_DEFAULT_HTTP_PROTOCOL", default="https") +ACCOUNT_AUTHENTICATED_LOGIN_REDIRECTS = LOGIN_REDIRECT_URL SOCIALACCOUNT_ADAPTER = "mycarehub.users.adapters.SocialAccountAdapter" SOCIALACCOUNT_AUTO_SIGNUP = True diff --git a/config/settings/test.py b/config/settings/test.py index 820842a..cbaa281 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -23,18 +23,6 @@ # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] -# TEMPLATES -# ------------------------------------------------------------------------------ -TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405 - ( - "django.template.loaders.cached.Loader", - [ - "django.template.loaders.filesystem.Loader", - "django.template.loaders.app_directories.Loader", - ], - ) -] - # EMAIL # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend diff --git a/config/urls.py b/config/urls.py index 652c909..3b570dd 100644 --- a/config/urls.py +++ b/config/urls.py @@ -140,4 +140,5 @@ urlpatterns += i18n_patterns( path("", include(wagtail_urls), name="wagtail"), + prefix_default_language=False, ) diff --git a/mycarehub/clients/forms.py b/mycarehub/clients/forms.py deleted file mode 100644 index adbde94..0000000 --- a/mycarehub/clients/forms.py +++ /dev/null @@ -1,45 +0,0 @@ -from django import forms - -from mycarehub.users.forms import validate_date_past -from mycarehub.users.models import GenderChoices - - -class ClientRegistrationForm(forms.Form): - client_id = forms.UUIDField( - required=True, label="Client ID", help_text="The client's unique user identifier" - ) - - name = forms.CharField( - required=True, - max_length=255, - label="Name", - help_text="The client's full name i.e family, given and other names, on one row", - ) - - gender = forms.ChoiceField( - required=True, - choices=GenderChoices.choices, - label="Gender", - help_text="Pick the client's gender from the drop-down list", - ) - - date_of_birth = forms.DateField( - required=True, - label="Date of birth", - help_text="Select the client's date of birth", - validators=[ - validate_date_past, - ], - ) - - organisation_id = forms.UUIDField( - required=True, - label="Organisation ID", - help_text="The client's currently assigned organisation", - ) - - program_id = forms.UUIDField( - required=True, - label="Program ID", - help_text="The client's currently assigned program", - ) diff --git a/mycarehub/clients/serializers.py b/mycarehub/clients/serializers.py index 33f8130..655c4a8 100644 --- a/mycarehub/clients/serializers.py +++ b/mycarehub/clients/serializers.py @@ -1,16 +1,16 @@ -from django import forms -from drf_braces.serializers.form_serializer import FormSerializer, make_form_serializer_field from rest_framework import serializers from mycarehub.common.serializers import OrganisationSerializer, ProgramSerializer -from .forms import ClientRegistrationForm from .models import Client class ClientSerializer(serializers.ModelSerializer): - organisation = OrganisationSerializer() - program = ProgramSerializer() + organisation = OrganisationSerializer(read_only=True) + program = ProgramSerializer(read_only=True) + organisation_id = serializers.UUIDField(write_only=True) + program_id = serializers.UUIDField(write_only=True) + client_id = serializers.UUIDField(write_only=True) class Meta: model = Client @@ -21,12 +21,7 @@ class Meta: "date_of_birth", "program", "organisation", + "program_id", + "organisation_id", + "client_id", ] - - -class ClientRegistrationSerializer(FormSerializer): - class Meta: - form = ClientRegistrationForm - field_mapping = { - forms.UUIDField: make_form_serializer_field(serializers.UUIDField), - } diff --git a/mycarehub/clients/views.py b/mycarehub/clients/views.py index 4e0970b..697a145 100644 --- a/mycarehub/clients/views.py +++ b/mycarehub/clients/views.py @@ -6,12 +6,12 @@ from mycarehub.common.models import Organisation, Program from .models import Client -from .serializers import ClientRegistrationSerializer, ClientSerializer +from .serializers import ClientSerializer class ClientAPIView(APIView): queryset = Client.objects.all() - serializer_class = ClientRegistrationSerializer + serializer_class = ClientSerializer def get(self, request): users = Client.objects.all() diff --git a/mycarehub/common/filters/__init__.py b/mycarehub/common/filters/__init__.py index 7e4f0c3..8aa7f6e 100644 --- a/mycarehub/common/filters/__init__.py +++ b/mycarehub/common/filters/__init__.py @@ -1,9 +1,7 @@ -from .base_filters import CommonFieldsFilterset from .common_filters import FacilityFilter, UserFacilityAllotmentFilter from .custom_filter_backends import OrganisationFilterBackend __all__ = [ - "CommonFieldsFilterset", "FacilityFilter", "OrganisationFilterBackend", "UserFacilityAllotmentFilter", diff --git a/mycarehub/common/filters/base_filters.py b/mycarehub/common/filters/base_filters.py deleted file mode 100644 index 46c37f5..0000000 --- a/mycarehub/common/filters/base_filters.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Base filters.""" -import django_filters -from django.db.models import Case, IntegerField, Value, When -from rest_framework.filters import SearchFilter - - -class CommonFieldsFilterset(django_filters.FilterSet): - """This is intended to be the base filter for most app filtersets.""" - - search = SearchFilter() - - def inactive_records(self, queryset, field, value): - """ - Select *inactive* records only. - - This function allows filtering of only the fields with active=False - To display the inactive records the query param should be set to - active=False or an equivalence as specified below - """ - return queryset.filter(active=value) - - def bubble_record_to_top(self, queryset, field, value): - """Ensure that the current record appears on top. - - This ensures that the record is available in select / comboboxes - even if it is a 'deep' entry that would otherwise be buried by - pagination. - """ - ordering_filters = ("custom_order", "-updated", "-created") - annotated_qs = queryset.annotate( - custom_order=Case( - When(pk=value, then=Value(0)), - output_field=IntegerField(), - default=Value(1), - ) - ).order_by(*ordering_filters) - - # guarantee that an entry with the supplied PK is *always* returned - alternate_qs = ( - queryset.model.objects.filter(pk=value) - .annotate( - custom_order=Case( - When(pk=value, then=Value(0)), - output_field=IntegerField(), - default=Value(1), - ) - ) - .order_by(*ordering_filters) - ) - return annotated_qs if annotated_qs.count() > 0 else alternate_qs - - active = django_filters.BooleanFilter(method="inactive_records") - bubble_record = django_filters.UUIDFilter(method="bubble_record_to_top") - combobox = django_filters.UUIDFilter(method="bubble_record_to_top") diff --git a/mycarehub/common/filters/common_filters.py b/mycarehub/common/filters/common_filters.py index c36b3d0..8c96933 100644 --- a/mycarehub/common/filters/common_filters.py +++ b/mycarehub/common/filters/common_filters.py @@ -1,10 +1,10 @@ +import django_filters from rest_framework import filters from ..models import Facility, UserFacilityAllotment -from .base_filters import CommonFieldsFilterset -class FacilityFilter(CommonFieldsFilterset): +class FacilityFilter(django_filters.FilterSet): """Filter facilities.""" search = filters.SearchFilter() @@ -16,7 +16,7 @@ class Meta: fields = "__all__" -class UserFacilityAllotmentFilter(CommonFieldsFilterset): +class UserFacilityAllotmentFilter(django_filters.FilterSet): search = filters.SearchFilter() class Meta: diff --git a/mycarehub/common/forms/__init__.py b/mycarehub/common/forms/__init__.py deleted file mode 100644 index 1969c9c..0000000 --- a/mycarehub/common/forms/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from .base_forms import BaseModelForm -from .common_forms import ( - FacilityForm, - OrganisationForm, - OrganisationRegistrationForm, - ProgramRegistrationForm, - UserFacilityAllotmentForm, -) - -__all__ = [ - "BaseModelForm", - "FacilityForm", - "OrganisationForm", - "UserFacilityAllotmentForm", - "OrganisationRegistrationForm", - "ProgramRegistrationForm", -] diff --git a/mycarehub/common/forms/base_forms.py b/mycarehub/common/forms/base_forms.py deleted file mode 100644 index e239ca7..0000000 --- a/mycarehub/common/forms/base_forms.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Any, Dict - -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Submit -from django.forms import ModelForm - - -class BaseModelForm(ModelForm): - """Base form for the majority of model forms in the project.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._context: Dict[str, Any] = kwargs.pop("context", {}) - self.helper = FormHelper(self) - self.helper.form_method = "post" - self.helper.form_action = "" - self.helper.add_input(Submit("submit", "Save")) - self.helper.html5_required = True - - class Meta: - exclude = ( - "created", - "updated", - "created_by", - "updated_by", - "organisation", - ) diff --git a/mycarehub/common/forms/common_forms.py b/mycarehub/common/forms/common_forms.py deleted file mode 100644 index dc45a8f..0000000 --- a/mycarehub/common/forms/common_forms.py +++ /dev/null @@ -1,198 +0,0 @@ -from crispy_forms.layout import Field, Fieldset, Layout -from django.forms import ( - CharField, - EmailField, - Form, - IntegerField, - MultipleChoiceField, - TextInput, - UUIDField, -) -from phonenumber_field.formfields import PhoneNumberField - -from ..dashboard import get_mycarehub_facilities_queryset -from ..models import Facility, Organisation, UserFacilityAllotment -from ..utils import get_constituencies, get_counties, get_sub_counties, get_wards -from ..widgets import MultiSearchableComboBox, SearchableComboBox -from .base_forms import BaseModelForm - - -class FacilityForm(BaseModelForm): - field_order = ( - "name", - "mfl_code", - "active", - "is_mycarehub_facility", - "county", - "phone", - "sub_county", - "constituency", - "ward", - "lon", - "lat", - "operation_status", - "registration_number", - # others as defined on the model - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.helper.form_id = "facility_form" - - class Meta: - model = Facility - widgets = { - "name": TextInput( - attrs={ - "required": True, - "size": 64, - } - ), - } - exclude = ( - "created", - "updated", - "created_by", - "updated_by", - "organisation", - "public_visible", - "approved", - "closed", - "open_late_night", - "open_weekends", - "open_public_holidays", - "open_whole_day", - "beds", - "cots", - "facility_owner", - "regulatory_body", - ) - - -class OrganisationForm(BaseModelForm): - class Meta: - model = Organisation - fields = "__all__" - - -class OrganisationRegistrationForm(Form): - name = CharField( - required=True, - max_length=255, - label="Name", - help_text="The organisation's name", - ) - - organisation_id = UUIDField( - required=True, - label="Organisation ID", - help_text="The organisation identifier", - ) - - email = EmailField( - required=True, - max_length=255, - label="Email", - help_text="The organisation's name", - ) - - phone_number = PhoneNumberField( - required=True, - max_length=255, - label="Phone Number", - help_text="The organisation's phone number", - ) - - code = IntegerField( - required=True, - label="Code", - help_text="The organisation's code", - ) - - -class ProgramRegistrationForm(Form): - name = CharField( - required=True, - max_length=255, - label="Name", - help_text="The program's name", - ) - - organisation_id = UUIDField( - required=True, - label="Organisation ID", - help_text="The program the organisation belongs to", - ) - - program_id = UUIDField( - required=True, - label="Program ID", - help_text="The program identifier", - ) - - -class UserFacilityAllotmentForm(BaseModelForm): - counties = MultipleChoiceField( - choices=get_counties(), - help_text=( - "All the facilities in the selected counties will be allocated to the selected user." - ), - required=False, - ) - constituencies = MultipleChoiceField( - choices=get_constituencies(), - help_text=( - "All the facilities in the selected constituencies will be allocated to the selected " - "user." - ), - required=False, - widget=MultiSearchableComboBox, - ) - sub_counties = MultipleChoiceField( - choices=get_sub_counties(), - help_text=( - "All the facilities in the selected sub counties will be allocated to the selected " - "user." - ), - required=False, - widget=MultiSearchableComboBox, - ) - wards = MultipleChoiceField( - choices=get_wards(), - help_text=( - "All the facilities in the selected wards will be allocated to the selected user." - ), - required=False, - widget=MultiSearchableComboBox, - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["facilities"].queryset = get_mycarehub_facilities_queryset() - self.helper.form_id = "user_facility_allotment_form" - self.helper.layout = Layout( - Field("user"), - Field("allotment_type"), - Fieldset("Allot by Facility", "facilities", css_id="allot_by_facility_fieldset"), - Fieldset( - "Allot by Region", - "region_type", - "counties", - "constituencies", - "sub_counties", - "wards", - css_id="allot_by_region_fieldset", - ), - Field("active"), - ) - - class Meta(BaseModelForm.Meta): - model = UserFacilityAllotment - fields = "__all__" - widgets = { - "facilities": MultiSearchableComboBox(), - "user": SearchableComboBox(), - } - - class Media: - js = ("js/user_facility_allotment_form.min.js",) diff --git a/mycarehub/common/migrations/0008_alter_organisation_email_address_and_more.py b/mycarehub/common/migrations/0008_alter_organisation_email_address_and_more.py new file mode 100644 index 0000000..9bbe450 --- /dev/null +++ b/mycarehub/common/migrations/0008_alter_organisation_email_address_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.6 on 2023-11-01 20:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("common", "0007_alter_auditlog_organisation_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="organisation", + name="email_address", + field=models.EmailField(db_column="email_address", max_length=100), + ), + migrations.RenameField( + model_name="organisation", + old_name="email_address", + new_name="email", + ), + migrations.AlterField( + model_name="organisation", + name="organisation_name", + field=models.CharField(db_column="organisation_name", max_length=100, unique=True), + ), + migrations.RenameField( + model_name="organisation", + old_name="organisation_name", + new_name="name", + ), + ] diff --git a/mycarehub/common/models/common_models.py b/mycarehub/common/models/common_models.py index a5785b2..3bf1532 100644 --- a/mycarehub/common/models/common_models.py +++ b/mycarehub/common/models/common_models.py @@ -4,7 +4,6 @@ from django.core.exceptions import ValidationError from django.db.models import Q from django.db.models.fields.json import JSONField -from django.urls import reverse from django.utils import timezone from ..utils import get_constituencies, get_counties, get_sub_counties, get_wards @@ -65,10 +64,6 @@ class Facility(AbstractBase): "check_facility_name_longer_than_three_characters", ] - def get_absolute_url(self): - update_url = reverse("common:facility_update", kwargs={"pk": self.pk}) - return update_url - def check_facility_name_longer_than_three_characters(self): if len(self.name) < 3: raise ValidationError("the facility name should exceed 3 characters") @@ -224,10 +219,6 @@ def check_ward_is_provided_if_region_type_is_ward(self): code="required", ) - def get_absolute_url(self): - update_url = reverse("common:user_facility_allotment_update", kwargs={"pk": self.pk}) - return update_url - def __str__(self): return ( f"User: {self.user.name}; Allotment Type: {self.get_allotment_type_display()}" # noqa diff --git a/mycarehub/common/models/organisation_models.py b/mycarehub/common/models/organisation_models.py index 2759704..9e54d4e 100644 --- a/mycarehub/common/models/organisation_models.py +++ b/mycarehub/common/models/organisation_models.py @@ -83,11 +83,11 @@ class Organisation(OrganisationAbstractBase): help_text="A unique code representing organisation registration", default="", ) - organisation_name = models.CharField(max_length=100, unique=True) + name = models.CharField(max_length=100, unique=True, db_column="organisation_name") # To be used by Branches, Departments and Department units # Branches belong to a certain organisation - email_address = models.EmailField(max_length=100) + email = models.EmailField(max_length=100, db_column="email_address") phone_number = PhoneNumberField() description = models.TextField(null=True, blank=True) postal_address = models.CharField(max_length=100, blank=True) @@ -96,15 +96,13 @@ class Organisation(OrganisationAbstractBase): def __str__(self): """Represent an organisation using it's name.""" - return self.organisation_name + return self.name def _set_organisation_code(self): code_sequence = _get_next_organisation_code_in_sequence() pad_code = str(code_sequence).zfill(4) - filtered_org_name = "".join( - c for c in self.organisation_name if c not in r"\?:!/();$%^&*)" - ) - count = len(self.organisation_name.split()) + filtered_org_name = "".join(c for c in self.name if c not in r"\?:!/();$%^&*)") + count = len(self.name.split()) if count >= 2: first_letters = [i[0].upper() for i in filtered_org_name.split()] name = "".join(first_letters) diff --git a/mycarehub/common/models/utils.py b/mycarehub/common/models/utils.py index b38983d..94f48e8 100644 --- a/mycarehub/common/models/utils.py +++ b/mycarehub/common/models/utils.py @@ -16,7 +16,7 @@ def unique_list(list_object): def get_directory(instance, filename): # pragma: nocover """Determine the upload_to path for every model inheriting Attachment.""" - org = instance.organisation.organisation_name + org = instance.organisation.name app = instance._meta.app_label # noqa return "{}/{}/{}/{}".format( org, app, instance.__class__.__name__.lower(), filename diff --git a/mycarehub/common/serializers/__init__.py b/mycarehub/common/serializers/__init__.py index 2529362..8a8ca0a 100644 --- a/mycarehub/common/serializers/__init__.py +++ b/mycarehub/common/serializers/__init__.py @@ -2,22 +2,17 @@ from .base_serializers import BaseSerializer from .common_serializers import ( FacilitySerializer, - OrganisationRegistrationSerializer, OrganisationSerializer, - ProgramRegistrationSerializer, ProgramSerializer, UserFacilityAllotmentSerializer, ) -from .mixins import AuditFieldsMixin, PartialResponseMixin +from .mixins import AuditFieldsMixin __all__ = [ "AuditFieldsMixin", "BaseSerializer", - "PartialResponseMixin", "FacilitySerializer", "UserFacilityAllotmentSerializer", "OrganisationSerializer", "ProgramSerializer", - "OrganisationRegistrationSerializer", - "ProgramRegistrationSerializer", ] diff --git a/mycarehub/common/serializers/base_serializers.py b/mycarehub/common/serializers/base_serializers.py index 2a702b9..3c75358 100644 --- a/mycarehub/common/serializers/base_serializers.py +++ b/mycarehub/common/serializers/base_serializers.py @@ -1,8 +1,6 @@ """Base serializers used in the project.""" import logging -from rest_framework import serializers - from .mixins import AuditFieldsMixin LOGGER = logging.getLogger(__name__) @@ -11,10 +9,5 @@ class BaseSerializer(AuditFieldsMixin): """Base class intended for inheritance by 'regular' app serializers.""" - url = serializers.URLField(source="get_absolute_url", read_only=True) - class Meta: - datatables_always_serialize = ( - "id", - "url", - ) + datatables_always_serialize = ("id",) diff --git a/mycarehub/common/serializers/common_serializers.py b/mycarehub/common/serializers/common_serializers.py index 7e9ca26..49b3826 100644 --- a/mycarehub/common/serializers/common_serializers.py +++ b/mycarehub/common/serializers/common_serializers.py @@ -2,13 +2,9 @@ import logging import uuid -from django import forms from django.contrib.auth import get_user_model -from drf_braces.serializers.form_serializer import FormSerializer, make_form_serializer_field -from phonenumber_field.formfields import PhoneNumberField from rest_framework import serializers -from mycarehub.common.forms import OrganisationRegistrationForm, ProgramRegistrationForm from mycarehub.common.models import Organisation, Program from ..models import Facility, UserFacilityAllotment @@ -47,15 +43,20 @@ class Meta(BaseSerializer.Meta): class OrganisationSerializer(BaseSerializer): + organisation_id = serializers.UUIDField(write_only=True) + class Meta(BaseSerializer.Meta): model = Organisation - fields = ["id", "organisation_name", "code"] + fields = ["id", "name", "code", "organisation_id", "phone_number", "email"] class ProgramSerializer(BaseSerializer): + organisation_id = serializers.UUIDField(write_only=True) + program_id = serializers.UUIDField(write_only=True) + class Meta(BaseSerializer.Meta): model = Program - fields = ["id", "name", "facilities"] + fields = ["id", "name", "facilities", "organisation_id", "program_id"] def update(self, instance, validated_data): facilities = validated_data.pop("facilities", None) @@ -66,20 +67,3 @@ def update(self, instance, validated_data): instance.facilities.add(facility) return super().update(instance, validated_data) - - -class OrganisationRegistrationSerializer(FormSerializer): - class Meta: - form = OrganisationRegistrationForm - field_mapping = { - forms.UUIDField: make_form_serializer_field(serializers.UUIDField), - PhoneNumberField: make_form_serializer_field(serializers.CharField), - } - - -class ProgramRegistrationSerializer(FormSerializer): - class Meta: - form = ProgramRegistrationForm - field_mapping = { - forms.UUIDField: make_form_serializer_field(serializers.UUIDField), - } diff --git a/mycarehub/common/serializers/mixins.py b/mycarehub/common/serializers/mixins.py index 5c1a72d..4cb81bd 100644 --- a/mycarehub/common/serializers/mixins.py +++ b/mycarehub/common/serializers/mixins.py @@ -29,33 +29,7 @@ def get_organisation(request, initial_data=None): return user.organisation -class PartialResponseMixin(object): - """Mixin that allows API clients to specify fields.""" - - def strip_fields(self, request, origi_fields): # noqa - """ - Select a subset of fields, determined by the `fields` parameter. - - Fetch a subset of fields from the serializer determined by the - request's ``fields`` query parameter. - - This is an initial implementation that does not handle: - - nested relationships - - rejection of unknown fields (currently ignoring unknown fields) - - wildcards - - e.t.c - """ - if request is None or request.method != "GET" or not hasattr(request, "query_params"): - return origi_fields - - fields = request.query_params.get("fields", None) - if isinstance(fields, str) and fields: - fields = [f.strip() for f in fields.split(",")] - return {field: origi_fields[field] for field in origi_fields if field in fields} - return origi_fields - - -class AuditFieldsMixin(PartialResponseMixin, serializers.ModelSerializer): +class AuditFieldsMixin(serializers.ModelSerializer): """Mixin for organisation, created, updated, created_by and updated_by.""" def __init__(self, *args, **kwargs): @@ -93,9 +67,3 @@ def update(self, instance, validated_data): """Ensure that audit fields are set when updating.""" self.populate_audit_fields(validated_data, False) return super().update(instance, validated_data) - - def get_fields(self): - """Implement support for responses that subset available fields.""" - origi_fields = super().get_fields() - request = self.context.get("request", None) - return self.strip_fields(request, origi_fields) diff --git a/mycarehub/common/tests/test_admin.py b/mycarehub/common/tests/test_admin.py index aee851d..fd1c112 100644 --- a/mycarehub/common/tests/test_admin.py +++ b/mycarehub/common/tests/test_admin.py @@ -3,7 +3,6 @@ from model_bakery import baker from mycarehub.common.admin import OrganisationAdmin -from mycarehub.common.forms import OrganisationForm from mycarehub.common.models import Organisation pytestmark = pytest.mark.django_db @@ -15,26 +14,20 @@ def organisation_admin(): return admin -@pytest.fixture -def organisation_form(): - form = OrganisationForm() - return form - - -def test_base_admin_update_created_by(request_with_user, organisation_admin, organisation_form): +def test_base_admin_update_created_by(request_with_user, organisation_admin): org = baker.prepare(Organisation) assert org.created_by is None assert org.updated_by is None - organisation_admin.save_model(request_with_user, org, organisation_form, change=False) + organisation_admin.save_model(request_with_user, org, form=None, change=False) assert org.created_by is not None assert org.updated_by is not None -def test_base_admin_update_updated_by(request_with_user, organisation_admin, organisation_form): +def test_base_admin_update_updated_by(request_with_user, organisation_admin): org = baker.make(Organisation) original_created_by = org.created_by original_updated_by = org.updated_by - organisation_admin.save_model(request_with_user, org, organisation_form, change=True) + organisation_admin.save_model(request_with_user, org, form=None, change=True) assert org.created_by == original_created_by assert org.updated_by != original_updated_by diff --git a/mycarehub/common/tests/test_api.py b/mycarehub/common/tests/test_api.py index 5169c38..9d18172 100644 --- a/mycarehub/common/tests/test_api.py +++ b/mycarehub/common/tests/test_api.py @@ -1,5 +1,4 @@ """Test for the common views.""" -import random import shutil import uuid from functools import partial @@ -8,7 +7,6 @@ import pytest from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission -from django.test import TestCase from django.urls import reverse from faker import Faker from model_bakery import baker @@ -39,18 +37,18 @@ def global_organisation(): """Create organisation for running test.""" org_id = "ebef581c-494b-4772-9e49-0b0755c44e61" code = 50 - organisation_name = "Demo Hospital" + name = "Demo Hospital" try: return Organisation.objects.get( id=org_id, code=code, - organisation_name=organisation_name, + name=name, ) except Organisation.DoesNotExist: return baker.make( Organisation, id=org_id, - organisation_name=organisation_name, + name=name, code=code, ) @@ -105,437 +103,6 @@ def extra_headers(self): return {} -class CRUDTestMixin(LoggedInMixin, APITestCase): - def setUp(self): - self.url_list = reverse("api:facility-list") - self.comparison_field = "name" - self.url_detail_base = "api:facility-detail" - self.instance = baker.make( - Facility, - organisation=self.global_organisation, - ) - self.data = { - "id": uuid.uuid4(), - "name": fake.name(), - "mfl_code": random.randint(1, 999_999_999), - "county": "Nairobi", - "organisation": self.global_organisation.pk, - } - self.detail_url = reverse(self.url_detail_base, kwargs={"pk": self.instance.pk}) - super().setUp() - - def test_create(self): - response = self.client.post(self.url_list, self.data) - assert response.status_code == 201, response.json() - assert response.data[self.comparison_field] == self.data[self.comparison_field] - - def test_list(self): - response = self.client.get(self.url_list) - assert response.status_code == 200, response.json() - assert response.data["count"] >= 1, response.json() - - values = [a[self.comparison_field] for a in response.data["results"]] - assert getattr(self.instance, self.comparison_field) in values - - def test_retrieve(self): - response = self.client.get(self.detail_url) - assert response.status_code == 200, response.json() - assert response.data[self.comparison_field] == getattr( - self.instance, self.comparison_field - ) - - def test_patch(self): - patch = {self.comparison_field: fake.ssn()} - response = self.client.patch(self.detail_url, patch) - assert response.status_code == 200, response.json() - assert response.data[self.comparison_field] == patch[self.comparison_field] - - def test_put(self): - response = self.client.put(self.detail_url, self.data) - assert response.status_code == 200, response.json() - assert response.data[self.comparison_field] == self.data[self.comparison_field] - - -class FacilityViewsetTest(LoggedInMixin, APITestCase): - """Test suite for facilities API.""" - - def setUp(self): - self.url_list = reverse("api:facility-list") - super().setUp() - - def test_create_facility(self): - """Test add facility.""" - data = { - "name": fake.name(), - "mfl_code": random.randint(1, 999_999_999), - "county": random.choice(WHITELIST_COUNTIES), - "is_mycarehub_facility": True, - "operation_status": "Operational", - "organisation": self.global_organisation.pk, - } - response = self.client.post(self.url_list, data) - assert response.status_code == 201, response.json() - assert response.data["mfl_code"] == data["mfl_code"] - - def test_create_facility_no_organisation(self): - """Test add facility.""" - data = { - "name": fake.name(), - "mfl_code": random.randint(1, 999_999_999), - "county": random.choice(WHITELIST_COUNTIES), - "is_mycarehub_facility": True, - "operation_status": "Operational", - # the user's organisation is used - } - - response = self.client.post(self.url_list, data) - assert response.status_code == 201, response.json() - assert response.data["mfl_code"] == data["mfl_code"] - - def test_create_facility_error_bad_organisation(self): - """Test add facility.""" - data = { - "name": fake.name(), - "mfl_code": random.randint(1, 999_999_999), - "county": random.choice(WHITELIST_COUNTIES), - "is_mycarehub_facility": True, - "operation_status": "Operational", - "organisation": uuid.uuid4(), # does not exist - } - - response = self.client.post(self.url_list, data) - assert response.status_code == 400, response.json() - print(response.json()) - assert "Ensure the organisation provided exists." in response.json()["organisation"] - - def test_retrieve_facility(self): - """Test retrieving facility.""" - facility = baker.make( - Facility, - county=random.choice(WHITELIST_COUNTIES), - organisation=self.global_organisation, - ) - - response = self.client.get(self.url_list) - assert response.status_code == 200, response.json() - assert response.data["count"] >= 1, response.json() - - facility_codes = [a["mfl_code"] for a in response.data["results"]] - assert facility.mfl_code in facility_codes - - def test_retrieve_facility_with_fields(self): - """Test retrieving facility.""" - facility = baker.make( - Facility, - county=random.choice(WHITELIST_COUNTIES), - organisation=self.global_organisation, - ) - - # updated and other audit fields are popped and not returned - url = f"{self.url_list}?fields=id,name,mfl_code," - "updated,created,updated_by,created_by,organisation" - response = self.client.get(url) - assert response.status_code == 200, response.json() - assert response.data["count"] >= 1, response.json() - - facility_codes = [a["mfl_code"] for a in response.data["results"]] - assert facility.mfl_code in facility_codes - - def test_retrieve_facility_with_combobox(self): - """Test retrieving facility.""" - facility = baker.make( - Facility, - county=random.choice(WHITELIST_COUNTIES), - organisation=self.global_organisation, - ) - - url = f"{self.url_list}?combobox={facility.pk}" - response = self.client.get(url) - assert response.status_code == 200, response.json() - assert response.data["count"] >= 1, response.json() - - facility_codes = [a["mfl_code"] for a in response.data["results"]] - assert facility.mfl_code in facility_codes - - def test_retrieve_facility_active(self): - """Test retrieving facility.""" - facility = baker.make( - Facility, - county=random.choice(WHITELIST_COUNTIES), - organisation=self.global_organisation, - ) - - url = f"{self.url_list}?active=True" - response = self.client.get(url) - assert response.status_code == 200, response.json() - assert response.data["count"] >= 1, response.json() - - facility_codes = [a["mfl_code"] for a in response.data["results"]] - assert facility.mfl_code in facility_codes - - def test_patch_facility(self): - """Test changing user facility.""" - facility = baker.make( - Facility, - county=random.choice(WHITELIST_COUNTIES), - organisation=self.global_organisation, - ) - - edit_code = {"mfl_code": 999999999} - url = reverse("api:facility-detail", kwargs={"pk": facility.pk}) - response = self.client.patch(url, edit_code) - - assert response.status_code == 200, response.json() - assert response.data["mfl_code"] == edit_code["mfl_code"] - - def test_put_facility(self): - """Test changing user and add new facility.""" - facility = baker.make( - Facility, - county=random.choice(WHITELIST_COUNTIES), - organisation=self.global_organisation, - ) - data = { - "name": fake.name(), - "mfl_code": random.randint(1, 999_999_999), - "county": random.choice(WHITELIST_COUNTIES), - "organisation": self.global_organisation.pk, - } - - url = reverse("api:facility-detail", kwargs={"pk": facility.pk}) - response = self.client.put(url, data) - - assert response.status_code == 200, response.json() - assert response.data["mfl_code"] == data["mfl_code"] - - -class FacilityFormTest(LoggedInMixin, TestCase): - def test_create(self): - data = { - "name": fake.name(), - "mfl_code": random.randint(1, 999_999_999), - "county": random.choice(WHITELIST_COUNTIES), - "phone": "+254722000000", - "is_mycarehub_facility": True, - "operation_status": "Operational", - "lon": 0.0, - "lat": 0.0, - "organisation": self.global_organisation.pk, - } - response = self.client.post(reverse("common:facility_create"), data=data) - self.assertEqual( - response.status_code, - 302, - ) - - def test_update(self): - facility = baker.make( - Facility, - county=random.choice(WHITELIST_COUNTIES), - organisation=self.global_organisation, - ) - data = { - "pk": facility.pk, - "name": fake.name(), - "mfl_code": random.randint(1, 999_999_999), - "county": random.choice(WHITELIST_COUNTIES), - "phone": "+254722000000", - "is_mycarehub_facility": True, - "operation_status": "Operational", - "lon": 0.0, - "lat": 0.0, - "organisation": self.global_organisation.pk, - } - response = self.client.post( - reverse("common:facility_update", kwargs={"pk": facility.pk}), data=data - ) - self.assertEqual( - response.status_code, - 302, - ) - - def test_delete(self): - facility = baker.make( - Facility, - county=random.choice(WHITELIST_COUNTIES), - organisation=self.global_organisation, - ) - response = self.client.post( - reverse("common:facility_delete", kwargs={"pk": facility.pk}), - ) - self.assertEqual( - response.status_code, - 302, - ) - - -class UserFacilityViewSetTest(LoggedInMixin, APITestCase): - def setUp(self): - super().setUp() - self.by_both = UserFacilityAllotment.AllotmentType.BY_FACILITY_AND_REGION - self.by_facility = UserFacilityAllotment.AllotmentType.BY_FACILITY - self.by_region = UserFacilityAllotment.AllotmentType.BY_REGION - self.facilities = baker.make( - Facility, - 20, - county="Nairobi", - organisation=self.global_organisation, - ) - - def test_create(self): - user = baker.make( - get_user_model(), organisation=self.global_organisation, program=self.program - ) - data = { - "allotment_type": self.by_facility.value, - "facilities": map(lambda f: f.pk, self.facilities), - "user": user.pk, - "organisation": self.global_organisation.pk, - } - response = self.client.post(reverse("api:userfacilityallotment-list"), data=data) - assert response.status_code == 201 - - def test_retrieve(self): - user = baker.make( - get_user_model(), organisation=self.global_organisation, program=self.program - ) - instance: UserFacilityAllotment = baker.make( - UserFacilityAllotment, - allotment_type=self.by_facility.value, - facilities=self.facilities, - organisation=self.global_organisation, - user=user, - ) - - response = self.client.get(reverse("api:userfacilityallotment-list")) - assert response.status_code == 200, response.json() - assert response.data["count"] >= 1, response.json() - - allotments = [entry["id"] for entry in response.data["results"]] - assert str(instance.pk) in allotments - - def test_patch(self): - user = baker.make( - get_user_model(), organisation=self.global_organisation, program=self.program - ) - instance: UserFacilityAllotment = baker.make( - UserFacilityAllotment, - allotment_type=self.by_facility.value, - facilities=self.facilities, - organisation=self.global_organisation, - user=user, - ) - - data = { - "allotment_type": self.by_region.value, - "region_type": UserFacilityAllotment.RegionType.COUNTY.value, - "counties": ["Nairobi"], - } - response = self.client.patch( - reverse("api:userfacilityallotment-detail", kwargs={"pk": instance.pk}), data - ) - assert response.status_code == 200, response.json() - assert response.data["allotment_type"] == data["allotment_type"] - assert response.data["region_type"] == data["region_type"] - assert response.data["counties"] == data["counties"] - - def test_put(self): - user = baker.make( - get_user_model(), organisation=self.global_organisation, program=self.program - ) - instance: UserFacilityAllotment = baker.make( - UserFacilityAllotment, - allotment_type=self.by_facility.value, - facilities=self.facilities, - organisation=self.global_organisation, - user=user, - ) - - data = { - "active": False, - "allotment_type": self.by_region.value, - "counties": ["Nairobi"], - "organisation": self.global_organisation.pk, - "region_type": UserFacilityAllotment.RegionType.COUNTY.value, - "user": user.pk, - } - response = self.client.put( - reverse("api:userfacilityallotment-detail", kwargs={"pk": instance.pk}), data - ) - assert response.status_code == 200, response.json() - assert response.data["active"] == data["active"] - assert response.data["allotment_type"] == data["allotment_type"] - assert response.data["region_type"] == data["region_type"] - assert response.data["counties"] == data["counties"] - - -class UserFacilityAllotmentFormTest(LoggedInMixin, TestCase): - def setUp(self): - super().setUp() - self.by_both = UserFacilityAllotment.AllotmentType.BY_FACILITY_AND_REGION - self.by_facility = UserFacilityAllotment.AllotmentType.BY_FACILITY - self.by_region = UserFacilityAllotment.AllotmentType.BY_REGION - self.facilities = baker.make( - Facility, - 20, - county="Nairobi", - organisation=self.global_organisation, - ) - - def test_create(self): - user = baker.make( - get_user_model(), organisation=self.global_organisation, program=self.program - ) - data = { - "allotment_type": self.by_facility.value, - "facilities": map(lambda f: f.pk, self.facilities), - "user": user.pk, - "organisation": self.global_organisation.pk, - } - response = self.client.post(reverse("common:user_facility_allotment_create"), data=data) - self.assertEqual( - response.status_code, - 302, - ) - - def test_update(self): - instance = self.user_facility_allotment - data = { - "pk": instance.pk, - "allotment_type": self.by_facility.value, - "facilities": map(lambda f: f.pk, self.facilities), - "user": self.user.pk, - "organisation": self.global_organisation.pk, - "active": False, - } - response = self.client.post( - reverse("common:user_facility_allotment_update", kwargs={"pk": instance.pk}), data=data - ) - self.assertEqual( - response.status_code, - 302, - ) - - def test_delete(self): - user = baker.make( - get_user_model(), organisation=self.global_organisation, program=self.program - ) - instance: UserFacilityAllotment = baker.make( - UserFacilityAllotment, - allotment_type=self.by_facility.value, - facilities=self.facilities, - organisation=self.global_organisation, - user=user, - ) - response = self.client.post( - reverse("common:user_facility_allotment_delete", kwargs={"pk": instance.pk}), - ) - self.assertEqual( - response.status_code, - 302, - ) - - def test_organisation_registration(user_with_all_permissions, client): client.force_login(user_with_all_permissions) url = reverse("api:organisations-general") @@ -691,3 +258,80 @@ def test_program_patch_invalid_data(user_with_all_permissions, client): ) assert response.status_code == status.HTTP_400_BAD_REQUEST + + +def test_list_facilities(user_with_all_permissions, client): + baker.make(Facility, _quantity=10, organisation=user_with_all_permissions.organisation) + + client.force_login(user_with_all_permissions) + url = reverse("api:facility-list") + + response = client.get( + url, + content_type="application/json", + accept="application/json", + ) + + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data["count"] == 10 + + response_two = client.post( + url, + data={ + "name": fake.name(), + "mfl_code": fake.random_int(min=100), + }, + content_type="application/json", + accept="application/json", + ) + + assert response_two.status_code == status.HTTP_201_CREATED + response_data = response_two.json() + assert response_data["id"] is not None + + response = client.get( + url, + content_type="application/json", + accept="application/json", + ) + + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data["count"] == 11 + + +def test_create_facility(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) + url = reverse("api:facility-list") + + response = client.post( + url, + data={ + "name": fake.name(), + "mfl_code": fake.random_int(min=100), + "organisation": str(fake.uuid4()), + }, + content_type="application/json", + accept="application/json", + ) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + + org = baker.make(Organisation) + response = client.post( + url, + data={ + "name": "Test Facility", + "mfl_code": fake.random_int(min=100), + "organisation": str(org.id), + }, + content_type="application/json", + accept="application/json", + ) + + assert response.status_code == status.HTTP_201_CREATED + + response_data = response.json() + assert response_data["id"] is not None + assert response_data["name"] == "Test Facility" diff --git a/mycarehub/common/tests/test_models.py b/mycarehub/common/tests/test_models.py index 5c4eb88..e44dc1e 100644 --- a/mycarehub/common/tests/test_models.py +++ b/mycarehub/common/tests/test_models.py @@ -104,7 +104,7 @@ def test_facility_error_saving(): def test_organisation_string_representation(): - org = baker.make("common.Organisation", organisation_name="Test Organisation") + org = baker.make("common.Organisation", name="Test Organisation") assert str(org) == "Test Organisation" @@ -279,7 +279,7 @@ def test_timezone(self): def test_owner(self): """Test for test owner.""" - org = baker.make(Organisation, organisation_name="Savannah Informatics") + org = baker.make(Organisation, name="Savannah Informatics") fake = baker.make( Facility, created=self.jana, @@ -440,15 +440,6 @@ def test_ward_must_be_provided_if_region_type_is_ward(self): in e2.value.messages ) - def test_get_absolute_url(self): - """Test the `self.get_absolute_url` method.""" - - allotment = self.user_facility_allotment - assert ( - allotment.get_absolute_url() - == "/common/user_facility_allotment_update/%s" % allotment.pk - ) - def test_get_facilities_for_user(self): """Tests for the `UserFacilityAllotment.get_facilities_for_user()` method.""" diff --git a/mycarehub/common/tests/test_views.py b/mycarehub/common/tests/test_views.py index 8ac8ebe..24f3fe4 100644 --- a/mycarehub/common/tests/test_views.py +++ b/mycarehub/common/tests/test_views.py @@ -29,17 +29,3 @@ def test_about_view(user_with_all_permissions, client): url = reverse("about") response = client.get(url) assert response.status_code == status.HTTP_200_OK - - -def test_facility_view(user_with_all_permissions, client): - client.force_login(user_with_all_permissions) - url = reverse("common:facilities") - response = client.get(url) - assert response.status_code == status.HTTP_200_OK - - -def test_user_facility_allotment_view(user_with_all_permissions, client): - client.force_login(user_with_all_permissions) - url = reverse("common:user_facility_allotments") - response = client.get(url) - assert response.status_code == status.HTTP_200_OK diff --git a/mycarehub/common/urls.py b/mycarehub/common/urls.py index c3d3f98..d49a806 100644 --- a/mycarehub/common/urls.py +++ b/mycarehub/common/urls.py @@ -1,52 +1,2 @@ -from django.urls import path - -from .views import ( - FacilityCreateView, - FacilityDeleteView, - FacilityUpdateView, - FacilityView, - UserFacilityAllotmentCreateView, - UserFacilityAllotmentDeleteView, - UserFacilityAllotmentUpdateView, - UserFacilityAllotmentView, -) - app_name = "common" -urlpatterns = [ - path("facilities", view=FacilityView.as_view(), name="facilities"), - path( - "facility_create", - view=FacilityCreateView.as_view(), - name="facility_create", - ), - path( - "facility_update/", - view=FacilityUpdateView.as_view(), - name="facility_update", - ), - path( - "facility_delete/", - view=FacilityDeleteView.as_view(), - name="facility_delete", - ), - path( - "user_facility_allotments", - view=UserFacilityAllotmentView.as_view(), - name="user_facility_allotments", - ), - path( - "user_facility_allotment_create", - view=UserFacilityAllotmentCreateView.as_view(), - name="user_facility_allotment_create", - ), - path( - "user_facility_allotment_update/", - view=UserFacilityAllotmentUpdateView.as_view(), - name="user_facility_allotment_update", - ), - path( - "user_facility_allotment_delete/", - view=UserFacilityAllotmentDeleteView.as_view(), - name="user_facility_allotment_delete", - ), -] +urlpatterns = [] diff --git a/mycarehub/common/views/common_views/__init__.py b/mycarehub/common/views/common_views/__init__.py index 3f2cb9d..fc9d46c 100644 --- a/mycarehub/common/views/common_views/__init__.py +++ b/mycarehub/common/views/common_views/__init__.py @@ -4,31 +4,12 @@ ProgramAPIView, UserFacilityViewSet, ) -from .vanilla_common_views import ( - AboutView, - FacilityCreateView, - FacilityDeleteView, - FacilityUpdateView, - FacilityView, - HomeView, - UserFacilityAllotmentCreateView, - UserFacilityAllotmentDeleteView, - UserFacilityAllotmentUpdateView, - UserFacilityAllotmentView, -) +from .vanilla_common_views import AboutView, HomeView __all__ = [ "AboutView", - "FacilityCreateView", - "FacilityDeleteView", - "FacilityUpdateView", - "FacilityView", "FacilityViewSet", "HomeView", - "UserFacilityAllotmentCreateView", - "UserFacilityAllotmentDeleteView", - "UserFacilityAllotmentUpdateView", - "UserFacilityAllotmentView", "UserFacilityViewSet", "OrganisationAPIView", "ProgramAPIView", diff --git a/mycarehub/common/views/common_views/drf_common_views.py b/mycarehub/common/views/common_views/drf_common_views.py index 8838820..b3029f7 100644 --- a/mycarehub/common/views/common_views/drf_common_views.py +++ b/mycarehub/common/views/common_views/drf_common_views.py @@ -8,9 +8,7 @@ from mycarehub.common.models import Organisation, Program, UserFacilityAllotment from mycarehub.common.serializers import ( FacilitySerializer, - OrganisationRegistrationSerializer, OrganisationSerializer, - ProgramRegistrationSerializer, ProgramSerializer, UserFacilityAllotmentSerializer, ) @@ -42,7 +40,7 @@ class UserFacilityViewSet(BaseView): class OrganisationAPIView(APIView): queryset = Organisation.objects.all() - serializer_class = OrganisationRegistrationSerializer + serializer_class = OrganisationSerializer def post(self, request, format=None): serializer = self.serializer_class(data=request.data) @@ -51,10 +49,10 @@ def post(self, request, format=None): validated_data = serializer.validated_data data = { "id": validated_data["organisation_id"], - "organisation_name": validated_data["name"], + "name": validated_data["name"], "code": validated_data["code"], "phone_number": validated_data["phone_number"], - "email_address": validated_data["email"], + "email": validated_data["email"], } new_org = Organisation.objects.create(**data) @@ -73,7 +71,7 @@ def post(self, request, format=None): class ProgramAPIView(APIView): queryset = Program.objects.all() - serializer_class = ProgramRegistrationSerializer + serializer_class = ProgramSerializer def get(self, request): programs = Program.objects.all() diff --git a/mycarehub/common/views/common_views/vanilla_common_views.py b/mycarehub/common/views/common_views/vanilla_common_views.py index 09c2b34..c34742e 100644 --- a/mycarehub/common/views/common_views/vanilla_common_views.py +++ b/mycarehub/common/views/common_views/vanilla_common_views.py @@ -1,12 +1,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin -from django.urls import reverse_lazy -from django.views.generic import CreateView, DeleteView, TemplateView, UpdateView +from django.views.generic import TemplateView -from mycarehub.common.dashboard import get_active_facility_count, get_active_user_count -from mycarehub.common.forms import FacilityForm, UserFacilityAllotmentForm -from mycarehub.common.models import Facility, UserFacilityAllotment - -from ..mixins import ApprovedMixin, BaseFormMixin +from ..mixins import ApprovedMixin class HomeView(LoginRequiredMixin, ApprovedMixin, TemplateView): @@ -18,10 +13,6 @@ def get_context_data(self, **kwargs): context["active"] = "dashboard-nav" # id of active nav element context["selected"] = "dashboard" # id of selected page - # dashboard summaries - user = self.request.user - context["active_facility_count"] = get_active_facility_count(user) - context["user_count"] = get_active_user_count(user) return context @@ -34,76 +25,3 @@ def get_context_data(self, **kwargs): context["active"] = "dashboard-nav" # id of active nav element context["selected"] = "dashboard" # id of selected page return context - - -class FacilityContextMixin: - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) # type: ignore - context["active"] = "facilities-nav" # id of active nav element - context["available_fields_url"] = reverse_lazy("api:facility-get-available-fields") - context["dump_data_url"] = reverse_lazy("api:facility-dump-data") - context["get_filter_form_url"] = reverse_lazy("api:facility-get-filter-form") - context["selected"] = "facilities" # id of selected page - return context - - -class FacilityView(FacilityContextMixin, LoginRequiredMixin, ApprovedMixin, TemplateView): - template_name = "pages/common/facilities.html" - permission_required = "common.view_facility" - - -class FacilityCreateView(FacilityContextMixin, BaseFormMixin, CreateView): - form_class = FacilityForm - success_url = reverse_lazy("common:facilities") - model = Facility - - -class FacilityUpdateView(FacilityContextMixin, UpdateView, BaseFormMixin): - form_class = FacilityForm - model = Facility - success_url = reverse_lazy("common:facilities") - - -class FacilityDeleteView(FacilityContextMixin, DeleteView, BaseFormMixin): - form_class = FacilityForm - model = Facility - success_url = reverse_lazy("common:facilities") - - -class UserFacilityAllotmentContextMixin: - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) # type: ignore - context["active"] = "facilities-nav" # id of active nav element - context["selected"] = "user-facility-allotments" # id of selected page - return context - - -class UserFacilityAllotmentView( - UserFacilityAllotmentContextMixin, LoginRequiredMixin, ApprovedMixin, TemplateView -): - template_name = "pages/common/user_facility_allotments.html" - permission_required = "common.view_userfacilityallotment" - - -class UserFacilityAllotmentCreateView( - UserFacilityAllotmentContextMixin, BaseFormMixin, CreateView -): - form_class = UserFacilityAllotmentForm - model = UserFacilityAllotment - success_url = reverse_lazy("common:user_facility_allotments") - - -class UserFacilityAllotmentUpdateView( - UserFacilityAllotmentContextMixin, BaseFormMixin, UpdateView -): - form_class = UserFacilityAllotmentForm - model = UserFacilityAllotment - success_url = reverse_lazy("common:user_facility_allotments") - - -class UserFacilityAllotmentDeleteView( - UserFacilityAllotmentContextMixin, BaseFormMixin, DeleteView -): - form_class = UserFacilityAllotmentForm - model = UserFacilityAllotment - success_url = reverse_lazy("common:user_facility_allotments") diff --git a/mycarehub/common/views/mixins/__init__.py b/mycarehub/common/views/mixins/__init__.py index 1d7e035..feba95e 100644 --- a/mycarehub/common/views/mixins/__init__.py +++ b/mycarehub/common/views/mixins/__init__.py @@ -1,6 +1,5 @@ -from .vanilla_mixins import ApprovedMixin, BaseFormMixin +from .vanilla_mixins import ApprovedMixin __all__ = [ "ApprovedMixin", - "BaseFormMixin", ] diff --git a/mycarehub/common/views/mixins/vanilla_mixins.py b/mycarehub/common/views/mixins/vanilla_mixins.py index 68c3600..e61ca28 100644 --- a/mycarehub/common/views/mixins/vanilla_mixins.py +++ b/mycarehub/common/views/mixins/vanilla_mixins.py @@ -1,8 +1,6 @@ from django.contrib.auth import get_user_model from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin -from django.utils import timezone from django.views.generic import View -from django.views.generic.edit import ModelFormMixin User = get_user_model() @@ -12,22 +10,3 @@ class ApprovedMixin(UserPassesTestMixin, PermissionRequiredMixin, View): def test_func(self): return self.request.user.is_authenticated - - -class BaseFormMixin(ModelFormMixin, View): - def form_valid(self, form): - user = self.request.user - instance = form.instance - instance.updated_by = user.pk - instance.updated = timezone.now() - - if instance.created_by is None: # pragma: nobranch - instance.created_by = user.pk - - if ( - getattr(instance, "organisation", None) is None - and isinstance(user, User) - and getattr(user, "organisation", None) is not None - ): - instance.organisation = user.organisation # pragma: nocover - return super().form_valid(form) diff --git a/mycarehub/common/widgets.py b/mycarehub/common/widgets.py deleted file mode 100644 index cacf10b..0000000 --- a/mycarehub/common/widgets.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Custom widgets to supplement/replace existing widgets. -""" -from django import forms - - -class SearchableComboBox(forms.Select): # pragma: nocover - """A combo box with search capabilities. - - This is suitable as a replacement for select with many options. - """ - - input_type = "select" - option_inherits_attrs = False - - def build_attrs(self, base_attrs, extra_attrs=None): - # The `data-live-search` attribute is needed to enable the search - # functionality. - base_attrs.update({"data-live-search": "true"}) - return super().build_attrs(base_attrs, extra_attrs) - - -class MultiSearchableComboBox(forms.SelectMultiple): - """A multi-valued combo box with search capabilities. - - This is suitable as a replacement for select multiple widget with many options. - """ - - def build_attrs(self, base_attrs, extra_attrs=None): # pragma: nocover - # The `data-live-search` attribute is needed to enable the search - # functionality. - base_attrs.update({"data-live-search": "true"}) - return super().build_attrs(base_attrs, extra_attrs) diff --git a/mycarehub/content/filters.py b/mycarehub/content/filters.py index f49619e..359d7c9 100644 --- a/mycarehub/content/filters.py +++ b/mycarehub/content/filters.py @@ -6,7 +6,6 @@ from wagtail.admin.filters import WagtailFilterSet from mycarehub.clients.models import Client -from mycarehub.common.filters.base_filters import CommonFieldsFilterset from mycarehub.common.models import ContentSequence from mycarehub.content.models import ContentItem from mycarehub.content.models.sms import SMSContentItem, SMSContentItemCategory, SMSContentItemTag @@ -156,7 +155,7 @@ def filter_queryset(self, request, queryset, view): return queryset -class ContentItemCategoryFilter(CommonFieldsFilterset): +class ContentItemCategoryFilter(django_filters.FilterSet): def category_has_content(self, queryset, field, value): """ Ensures the category has content in it @@ -171,25 +170,25 @@ class Meta: fields = "__all__" -class ContentViewFilter(CommonFieldsFilterset): +class ContentViewFilter(django_filters.FilterSet): class Meta: model = ContentView fields = "__all__" -class ContentShareFilter(CommonFieldsFilterset): +class ContentShareFilter(django_filters.FilterSet): class Meta: model = ContentShare fields = "__all__" -class ContentLikeFilter(CommonFieldsFilterset): +class ContentLikeFilter(django_filters.FilterSet): class Meta: model = ContentLike fields = "__all__" -class ContentBookmarkFilter(CommonFieldsFilterset): +class ContentBookmarkFilter(django_filters.FilterSet): class Meta: model = ContentBookmark fields = "__all__" diff --git a/mycarehub/content/migrations/0019_alter_contentitem_facilities_and_more.py b/mycarehub/content/migrations/0019_alter_contentitem_facilities_and_more.py new file mode 100644 index 0000000..beefe4c --- /dev/null +++ b/mycarehub/content/migrations/0019_alter_contentitem_facilities_and_more.py @@ -0,0 +1,79 @@ +# Generated by Django 4.2.6 on 2023-10-31 17:38 + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields +import wagtail.images.models + + +class Migration(migrations.Migration): + dependencies = [ + ("common", "0007_alter_auditlog_organisation_and_more"), + ("content", "0018_rename_content_smscontentitem_body"), + ] + + operations = [ + migrations.AlterField( + model_name="contentitem", + name="facilities", + field=modelcluster.fields.ParentalManyToManyField( + blank=True, + help_text="Determines which facilities content is meant for.", + to="common.facility", + ), + ), + migrations.AlterField( + model_name="customrendition", + name="file", + field=wagtail.images.models.WagtailImageField( + height_field="height", + storage=wagtail.images.models.get_rendition_storage, + upload_to=wagtail.images.models.get_rendition_upload_to, + width_field="width", + ), + ), + migrations.AlterField( + model_name="smscontentitem", + name="organisation", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(app_label)s_%(class)s_related", + to="common.organisation", + ), + ), + migrations.AlterField( + model_name="smscontentitem", + name="program", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(app_label)s_%(class)s_related", + to="common.program", + ), + ), + migrations.AlterField( + model_name="smscontentitemcategory", + name="organisation", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(app_label)s_%(class)s_related", + to="common.organisation", + ), + ), + migrations.AlterField( + model_name="smscontentitemtag", + name="organisation", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="%(app_label)s_%(class)s_related", + to="common.organisation", + ), + ), + ] diff --git a/mycarehub/content/models/models.py b/mycarehub/content/models/models.py index 7812d75..76d9516 100644 --- a/mycarehub/content/models/models.py +++ b/mycarehub/content/models/models.py @@ -201,7 +201,6 @@ class ItemTypes(models.TextChoices): Facility, help_text="Determines which facilities content is meant for.", blank=True, - null=True, ) date = models.DateField( diff --git a/mycarehub/content/signals.py b/mycarehub/content/signals.py index 308ef24..25b1044 100644 --- a/mycarehub/content/signals.py +++ b/mycarehub/content/signals.py @@ -151,10 +151,14 @@ def create_program_content_editor_permissions(sender, instance, created, **kwarg ) group.permissions.add(permission_object) - allowed_page_permissions = ["add", "edit", "publish"] + allowed_page_permissions = ["add_page", "change_page", "publish_page"] for permission in allowed_page_permissions: GroupPagePermission.objects.get_or_create( - group=group, page=instance, permission_type=permission + group=group, + page=instance, + permission=Permission.objects.get( + content_type=ContentType.objects.get_for_model(Page), codename=permission + ), ) root_collection = Collection.get_first_root_node() diff --git a/mycarehub/content/wagtail_hooks.py b/mycarehub/content/wagtail_hooks.py index 46ee209..fa03257 100644 --- a/mycarehub/content/wagtail_hooks.py +++ b/mycarehub/content/wagtail_hooks.py @@ -1,8 +1,8 @@ from django.shortcuts import redirect from django.urls import reverse from django.utils.safestring import mark_safe +from wagtail import hooks from wagtail.admin import messages -from wagtail.core import hooks from wagtail.documents import get_document_model from wagtail.images import get_image_model from wagtail.locales.wagtail_hooks import LocalesMenuItem diff --git a/mycarehub/mchprovider/provider.py b/mycarehub/mchprovider/provider.py index 2d52595..d995b22 100644 --- a/mycarehub/mchprovider/provider.py +++ b/mycarehub/mchprovider/provider.py @@ -1,3 +1,4 @@ +from allauth.socialaccount.adapter import get_adapter from allauth.socialaccount.providers.base import ProviderAccount, ProviderException from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider from django.core.exceptions import ObjectDoesNotExist @@ -14,6 +15,11 @@ class MycarehubProvider(OAuth2Provider): name = "Mycarehub" account_class = MycarehubAccount + def __init__(self, request, app=None): + if app is None: + app = get_adapter().get_app(request, self.id) + super().__init__(request, app=app) + def get_default_scope(self): return [] diff --git a/mycarehub/templates/common/facility_confirm_delete.html b/mycarehub/templates/common/facility_confirm_delete.html deleted file mode 100644 index b264093..0000000 --- a/mycarehub/templates/common/facility_confirm_delete.html +++ /dev/null @@ -1,33 +0,0 @@ - -{% extends "base.html" %} -{% load crispy_forms_tags %} -{% block title %} - Delete Facility -{% endblock title %} -{% block content %} -← Back -
-
-
-
-
-
-
-
-
-
-

Confirm Delete

-
-
- {% csrf_token %} -

Are you sure you want to delete "{{ object }}"?

- -
-
-
-
-
-
-
-
-{% endblock content %} diff --git a/mycarehub/templates/common/facility_form.html b/mycarehub/templates/common/facility_form.html deleted file mode 100644 index b4619b2..0000000 --- a/mycarehub/templates/common/facility_form.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends "base.html" %} {% load crispy_forms_tags %} {% block title %} -Add/Edit Facility {% endblock title %} {% block content %} -← Back -{% if perms.common.change_facility %} -
-
-
-
-
- {% if object.pk %} {% if perms.common.delete_facility %} - - - - - Delete Facility - - {% endif %} {% endif %} -
-
- -
-
-
-
-
-

- {% if object.pk %} Edit Facility {% else %} Create Facility - {%endif %} -

-
- {% crispy form %} -
-
-
-
-
-
-{% else %} -

You do not have permission to edit facilities.

-{% endif %} {% endblock content %} diff --git a/mycarehub/templates/common/userfacilityallotment_confirm_delete.html b/mycarehub/templates/common/userfacilityallotment_confirm_delete.html deleted file mode 100644 index 727fd09..0000000 --- a/mycarehub/templates/common/userfacilityallotment_confirm_delete.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "base.html" %} -{% load crispy_forms_tags %} -{% block title %} - Delete User Facility Allotment -{% endblock title %} -{% block content %} -← Back -
-
-
-
-
-
-
-
-
-
-

Confirm Delete

-
-
- {% csrf_token %} -

Are you sure you want to delete "{{ object }}"?

- -
-
-
-
-
-
-
-
-{% endblock content %} diff --git a/mycarehub/templates/common/userfacilityallotment_form.html b/mycarehub/templates/common/userfacilityallotment_form.html deleted file mode 100644 index ff70da9..0000000 --- a/mycarehub/templates/common/userfacilityallotment_form.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends "base.html" %} {% load crispy_forms_tags %} -{% block title %} - Add/Edit User Facility Allotment -{% endblock title %} -{% block content %} - ← Back -{% if perms.common.change_userfacilityallotment %} - -
-
-
-
-
- {% if object.pk %} {% if perms.common.delete_userfacilityallotment %} - - - - - Delete User Facility Allotment - - {% endif %} {% endif %} -
-
- -
-
-
-
-
-

- {% if object.pk %} - Edit User Facility Allotment - {% else %} - Create User Facility Allotment - {% endif %} -

-
- {% crispy form %} -
-
-
-
-
-
- -{% else %} -

You do not have permission to edit user facility allotments.

-{% endif %} -{% endblock content %} diff --git a/mycarehub/templates/fragments/head.html b/mycarehub/templates/fragments/head.html index 9cf466e..5f6f756 100644 --- a/mycarehub/templates/fragments/head.html +++ b/mycarehub/templates/fragments/head.html @@ -1,5 +1,4 @@ {% load static i18n compress%} -{% load wagtailfontawesome %} diff --git a/mycarehub/templates/fragments/sidebar.html b/mycarehub/templates/fragments/sidebar.html index 4fb8962..e3e656f 100644 --- a/mycarehub/templates/fragments/sidebar.html +++ b/mycarehub/templates/fragments/sidebar.html @@ -8,7 +8,6 @@ - {% include "fragments/atoms/facilities_menu.html" %} {% include "fragments/atoms/oauth_menu.html" %} diff --git a/mycarehub/templates/pages/common/facilities.html b/mycarehub/templates/pages/common/facilities.html deleted file mode 100644 index f825416..0000000 --- a/mycarehub/templates/pages/common/facilities.html +++ /dev/null @@ -1,81 +0,0 @@ -{% extends "base.html" %} -{% block title %}Facilities{% endblock %} -{% block content %} -

- Facilities -

-
-
-
- {% if perms.common.add_facility %} - - - - - Add Facility - - {% endif %} -
-
-
- - - - - - - - - - - - - -
NameMFL CodeCountySub-CountyWardKEPH LevelType
-
-
- - - -{% endblock content %} -{% block inline_javascript %} - -{% endblock inline_javascript %} diff --git a/mycarehub/templates/pages/common/user_facility_allotments.html b/mycarehub/templates/pages/common/user_facility_allotments.html deleted file mode 100644 index 2687100..0000000 --- a/mycarehub/templates/pages/common/user_facility_allotments.html +++ /dev/null @@ -1,69 +0,0 @@ -{% extends "base.html" %} -{% block title %}User Facility Allotments{% endblock %} -{% block content %} -

- User Facility Allotments -

-
-
-
- {% if perms.common.add_userfacilityallotment %} - - - - - Add Facility User - - {% endif %} -
-
-
-
- - - - - - - - -
UserAllotment Type
-
-
-
-{% endblock content %} -{% block inline_javascript %} - -{% endblock inline_javascript %} diff --git a/mycarehub/users/adapters.py b/mycarehub/users/adapters.py index fc3c2bd..e9a8f08 100644 --- a/mycarehub/users/adapters.py +++ b/mycarehub/users/adapters.py @@ -1,7 +1,7 @@ from typing import Any from allauth.account.adapter import DefaultAccountAdapter -from allauth.exceptions import ImmediateHttpResponse +from allauth.core.exceptions import ImmediateHttpResponse from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from django.conf import settings from django.contrib import messages @@ -49,3 +49,11 @@ def authentication_error( messages.add_message(request, messages.ERROR, str(exception)) raise ImmediateHttpResponse(redirect(reverse("wagtailadmin_login"))) + + def get_connect_redirect_url(self, request, socialaccount): + """ + Returns the default URL to redirect to after successfully + connecting a social account. + """ + url = reverse("wagtailadmin_redirect") + return url diff --git a/mycarehub/users/api/serializers.py b/mycarehub/users/api/serializers.py index 2d18a87..9c8ff33 100644 --- a/mycarehub/users/api/serializers.py +++ b/mycarehub/users/api/serializers.py @@ -1,17 +1,14 @@ -from django import forms from django.contrib.auth import get_user_model -from drf_braces.serializers.form_serializer import FormSerializer, make_form_serializer_field from rest_framework import serializers from mycarehub.common.serializers import OrganisationSerializer, ProgramSerializer -from mycarehub.users.forms import UserRegistrationForm User = get_user_model() class UserSerializer(serializers.ModelSerializer): - organisation = OrganisationSerializer() - program = ProgramSerializer() + organisation = OrganisationSerializer(read_only=True) + program = ProgramSerializer(read_only=True) class Meta: model = User @@ -25,11 +22,3 @@ class Meta: "program", "organisation", ] - - -class UserRegistrationSerializer(FormSerializer): - class Meta: - form = UserRegistrationForm - field_mapping = { - forms.UUIDField: make_form_serializer_field(serializers.UUIDField), - } diff --git a/mycarehub/users/api/views.py b/mycarehub/users/api/views.py index dce17bf..288ea7a 100644 --- a/mycarehub/users/api/views.py +++ b/mycarehub/users/api/views.py @@ -1,15 +1,11 @@ from django.contrib.auth import get_user_model -from django.db import transaction from rest_framework import status from rest_framework.decorators import action from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin from rest_framework.response import Response -from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet -from mycarehub.common.models import Organisation, Program - -from .serializers import UserRegistrationSerializer, UserSerializer +from .serializers import UserSerializer User = get_user_model() @@ -26,64 +22,3 @@ def get_queryset(self, *args, **kwargs): def me(self, request): serializer = UserSerializer(request.user, context={"request": request}) return Response(status=status.HTTP_200_OK, data=serializer.data) - - -class UserAPIView(APIView): - queryset = User.objects.all() - serializer_class = UserRegistrationSerializer - - def get(self, request): - users = User.objects.all() - serializer = UserSerializer(users, many=True) - - return Response(status=status.HTTP_200_OK, data=serializer.data) - - def post(self, request, format=None): - serializer = self.serializer_class(data=request.data) - if serializer.is_valid(): - try: - data = serializer.validated_data - - # get organisation - organisation = Organisation.objects.get(id=data["organisation_id"]) - - # get program - program = Program.objects.get(id=data["program_id"]) - - new_user, _ = User.objects.get_or_create( - id=data["user_id"], - defaults={ - "name": data["name"], - "username": data["username"], - "gender": data["gender"], - "date_of_birth": data["date_of_birth"], - "user_type": data["user_type"], - "organisation": organisation, - "program": program, - }, - ) - - serialized_user = UserSerializer(new_user) - return Response(serialized_user.data, status=status.HTTP_201_CREATED) - except Exception as e: # noqa # pragma: nocover - return Response( - {"exception": str(e)}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) # pragma: nocover - - return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - def delete(self, request, pk, format=None): - try: - user = User.objects.get(pk=pk) - - with transaction.atomic(): - user.delete() - - except Exception as e: # pragma: nocover - return Response( - {"exception": str(e)}, - status=status.HTTP_400_BAD_REQUEST, - ) # pragma: nocover - - return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/mycarehub/users/forms.py b/mycarehub/users/forms.py index fddf10e..9034970 100644 --- a/mycarehub/users/forms.py +++ b/mycarehub/users/forms.py @@ -1,14 +1,11 @@ from datetime import date -from django import forms from django.contrib.auth import forms as admin_forms from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from mycarehub.users.models import GenderChoices, UserTypes - User = get_user_model() @@ -36,58 +33,3 @@ class Meta(admin_forms.UserCreationForm.Meta): model = User error_messages = {"username": {"unique": _("This username has already been taken.")}} - - -class UserRegistrationForm(forms.Form): - user_id = forms.UUIDField( - required=True, label="User ID", help_text="The user's unique user identifier" - ) - - name = forms.CharField( - required=True, - max_length=255, - label="Name", - help_text="The user's full name i.e family, given and other names, on one row", - ) - - username = forms.CharField( - required=True, - max_length=255, - label="Username", - help_text="The user's unique username", - ) - - gender = forms.ChoiceField( - required=True, - choices=GenderChoices.choices, - label="Gender", - help_text="Pick the user's gender from the drop-down list", - ) - - date_of_birth = forms.DateField( - required=True, - label="Date of birth", - help_text="Select the user's date of birth", - validators=[ - validate_date_past, - ], - ) - - user_type = forms.ChoiceField( - required=True, - choices=UserTypes.choices, - label="User Type", - help_text="Pick the user's user type from the drop-down list", - ) - - organisation_id = forms.UUIDField( - required=True, - label="Organisation ID", - help_text="The user's currently assigned organisation", - ) - - program_id = forms.UUIDField( - required=True, - label="Program ID", - help_text="The user's currently assigned program", - ) diff --git a/mycarehub/users/tests/test_adapters.py b/mycarehub/users/tests/test_adapters.py index 8203883..76209f4 100644 --- a/mycarehub/users/tests/test_adapters.py +++ b/mycarehub/users/tests/test_adapters.py @@ -67,3 +67,11 @@ def test_authentication_error(self): with pytest.raises(ImmediateHttpResponse): social_adapter.authentication_error(request=req, provider_id="id") + + def test_get_connect_redirect_url(self): + req = MagicMock() + social_adapter = SocialAccountAdapter() + + url = social_adapter.get_connect_redirect_url(request=req, socialaccount=None) + + assert url == "/admin/redirect/" diff --git a/mycarehub/users/tests/test_drf_urls.py b/mycarehub/users/tests/test_drf_urls.py deleted file mode 100644 index 3ffef38..0000000 --- a/mycarehub/users/tests/test_drf_urls.py +++ /dev/null @@ -1,88 +0,0 @@ -import pytest -from django.urls import reverse -from faker import Faker -from model_bakery import baker -from rest_framework import status - -from mycarehub.users.models import GenderChoices, User, UserTypes - -fake = Faker() -pytestmark = pytest.mark.django_db - - -def test_user_list(user_with_all_permissions, client): - client.force_login(user_with_all_permissions) - url = reverse("api:users-general") - - response = client.get( - url, - content_type="application/json", - accept="application/json", - ) - - assert response.status_code == status.HTTP_200_OK - - response_data = response.json() - assert len(response_data) > 0 - - -def test_user_registration(user_with_all_permissions, client): - client.force_login(user_with_all_permissions) - url = reverse("api:users-general") - org = user_with_all_permissions.organisation - program = user_with_all_permissions.program - - response = client.post( - url, - data={ - "user_id": fake.uuid4(), - "name": fake.name(), - "username": fake.name(), - "user_type": UserTypes.CLIENT, - "organisation_id": org.id, - "program_id": program.id, - "gender": GenderChoices.MALE, - "date_of_birth": fake.date_of_birth(minimum_age=18), - }, - content_type="application/json", - accept="application/json", - ) - - assert response.status_code == status.HTTP_201_CREATED - - response_data = response.json() - assert response_data["id"] is not None - - -def test_user_registration_invalid_input(user_with_all_permissions, client): - client.force_login(user_with_all_permissions) - url = reverse("api:users-general") - - response = client.post( - url, - data={ - "user_id": fake.uuid4(), - "name": fake.name(), - "username": fake.name(), - "user_type": UserTypes.CLIENT, - "gender": GenderChoices.MALE, - "date_of_birth": fake.date_of_birth(minimum_age=18), - }, - content_type="application/json", - accept="application/json", - ) - - assert response.status_code == status.HTTP_400_BAD_REQUEST - - -def test_user_remove(user_with_all_permissions, client): - client.force_login(user_with_all_permissions) - user = baker.make(User) - url = reverse("api:users-detail", kwargs={"pk": user.pk}) - response = client.delete( - url, - content_type="application/json", - accept="application/json", - ) - - assert response.status_code == status.HTTP_204_NO_CONTENT diff --git a/mycarehub/users/tests/test_forms.py b/mycarehub/users/tests/test_forms.py index fcf3366..025f4b5 100644 --- a/mycarehub/users/tests/test_forms.py +++ b/mycarehub/users/tests/test_forms.py @@ -21,6 +21,9 @@ def test_validate_date_past(): with pytest.raises(ValidationError): validate_date_past("not a date") + past_date = date.today() - timedelta(days=4) + validate_date_past(past_date) + class TestUserCreationForm: """ diff --git a/mycarehub/users/views.py b/mycarehub/users/views.py index 74a3aac..ad6a175 100644 --- a/mycarehub/users/views.py +++ b/mycarehub/users/views.py @@ -59,6 +59,7 @@ def get_success_url(self): index_page = ( ContentItemIndexPage.objects.filter(program=user.program).order_by("id").first() ) + return reverse("wagtailadmin_explore", kwargs={"parent_page_id": index_page.id}) diff --git a/mycarehub/utils/general_utils.py b/mycarehub/utils/general_utils.py index 440e9df..74ef2ff 100644 --- a/mycarehub/utils/general_utils.py +++ b/mycarehub/utils/general_utils.py @@ -14,8 +14,8 @@ def default_organisation(): code=DEFAULT_ORG_CODE, id=settings.DEFAULT_ORG_ID, defaults={ - "organisation_name": settings.ORGANISATION_NAME, - "email_address": settings.ORGANISATION_EMAIL, + "name": settings.ORGANISATION_NAME, + "email": settings.ORGANISATION_EMAIL, "phone_number": settings.ORGANISATION_PHONE, }, ) @@ -27,15 +27,18 @@ def default_organisation(): def default_program(): try: + from django.db.models.signals import post_save + from mycarehub.common.models import Program # intentional late import - org, _ = Program.objects.get_or_create( + program, created = Program.objects.get_or_create( id=settings.DEFAULT_PROGRAM_ID, defaults={ "name": f"{settings.ORGANISATION_NAME}", }, ) - return org.pk + post_save.send(Program, instance=program, created=created) + return program.pk except (ProgrammingError, Exception): # pragma: nocover # this will occur during initial migrations on a clean db return uuid.UUID(settings.DEFAULT_PROGRAM_ID) diff --git a/package-lock.json b/package-lock.json index 81b821a..95ffe17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "jquery.easing": "^1.4.1", "jstree": "^3.3.12", "jszip": "^3.10.1", - "mjml": "^4.13.0", + "mjml": "^4.14.1", "pdfmake": "^0.2.7", "pg": "^8.8.0", "popper.js": "^1.16.1", @@ -67,11 +67,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -302,6 +302,11 @@ "node": ">= 8" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + }, "node_modules/@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", @@ -1955,17 +1960,17 @@ } }, "node_modules/cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", "dependencies": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" }, "engines": { "node": ">= 6" @@ -1975,20 +1980,138 @@ } }, "node_modules/cheerio-select": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", - "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "dependencies": { - "css-select": "^4.3.0", - "css-what": "^6.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.3.1", - "domutils": "^2.8.0" + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cheerio-select/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio-select/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2556,6 +2679,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", @@ -3354,9 +3478,9 @@ } }, "node_modules/detect-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "node_modules/dev-ip": { "version": "1.0.1", @@ -3571,23 +3695,81 @@ } }, "node_modules/editorconfig": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", - "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", "dependencies": { - "commander": "^2.19.0", - "lru-cache": "^4.1.5", - "semver": "^5.6.0", - "sigmund": "^1.0.1" + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/editorconfig/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/editorconfig/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/editorconfig/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/ee-first": { "version": "1.1.1", @@ -6538,9 +6720,9 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -6549,10 +6731,61 @@ } ], "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/http-cache-semantics": { @@ -7758,13 +7991,13 @@ "integrity": "sha512-BVpRacWCbNfo/ALWxnLkIY/WRa4Ydg/LtwzIJZvDm7vrhV8Txv+ACi6EGnU11zT19sTc3KEPathWx6CtjWLD1w==" }, "node_modules/js-beautify": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.7.tgz", - "integrity": "sha512-5SOX1KXPFKx+5f6ZrPsIPEY7NwKeQz47n3jm2i+XeHx9MoRsfQenlOP13FQhWvg8JRS0+XLO6XYUQ2GX+q+T9A==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.9.tgz", + "integrity": "sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==", "dependencies": { "config-chain": "^1.1.13", - "editorconfig": "^0.15.3", - "glob": "^8.0.3", + "editorconfig": "^1.0.3", + "glob": "^8.1.0", "nopt": "^6.0.0" }, "bin": { @@ -7773,7 +8006,7 @@ "js-beautify": "js/bin/js-beautify.js" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/js-beautify/node_modules/brace-expansion": { @@ -7785,9 +8018,9 @@ } }, "node_modules/js-beautify/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7803,9 +8036,9 @@ } }, "node_modules/js-beautify/node_modules/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -7888,15 +8121,15 @@ } }, "node_modules/juice": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/juice/-/juice-7.0.0.tgz", - "integrity": "sha512-AjKQX31KKN+uJs+zaf+GW8mBO/f/0NqSh2moTMyvwBY+4/lXIYTU8D8I2h6BAV3Xnz6GGsbalUyFqbYMe+Vh+Q==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/juice/-/juice-9.1.0.tgz", + "integrity": "sha512-odblShmPrUoHUwRuC8EmLji5bPP2MLO1GL+gt4XU3tT2ECmbSrrMjtMQaqg3wgMFP2zvUzdPZGfxc5Trk3Z+fQ==", "dependencies": { - "cheerio": "^1.0.0-rc.3", - "commander": "^5.1.0", + "cheerio": "^1.0.0-rc.12", + "commander": "^6.1.0", "mensch": "^0.3.4", "slick": "^1.12.2", - "web-resource-inliner": "^5.0.0" + "web-resource-inliner": "^6.0.1" }, "bin": { "juice": "bin/juice" @@ -7906,9 +8139,9 @@ } }, "node_modules/juice/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "engines": { "node": ">= 6" } @@ -8328,6 +8561,8 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "optional": true, "dependencies": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -8819,15 +9054,15 @@ } }, "node_modules/mjml": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.13.0.tgz", - "integrity": "sha512-OnFKESouLshz8DPFSb6M/dE8GkhiJnoy6LAam5TiLA1anAj24yQ2ZH388LtQoEkvTisqwiTmc9ejDh5ctnFaJQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.14.1.tgz", + "integrity": "sha512-f/wnWWIVbeb/ge3ff7c/KYYizI13QbGIp03odwwkCThsJsacw4gpZZAU7V4gXY3HxSXP2/q3jxOfaHVbkfNpOQ==", "dependencies": { "@babel/runtime": "^7.14.6", - "mjml-cli": "4.13.0", - "mjml-core": "4.13.0", - "mjml-migrate": "4.13.0", - "mjml-preset-core": "4.13.0", + "mjml-cli": "4.14.1", + "mjml-core": "4.14.1", + "mjml-migrate": "4.14.1", + "mjml-preset-core": "4.14.1", "mjml-validator": "4.13.0" }, "bin": { @@ -8835,49 +9070,49 @@ } }, "node_modules/mjml-accordion": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.13.0.tgz", - "integrity": "sha512-E3yihZW5Oq2p+sWOcr8kWeRTROmiTYOGxB4IOxW/jTycdY07N3FX3e6vuh7Fv3rryHEUaydUQYto3ICVyctI7w==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.14.1.tgz", + "integrity": "sha512-dpNXyjnhYwhM75JSjD4wFUa9JgHm86M2pa0CoTzdv1zOQz67ilc4BoK5mc2S0gOjJpjBShM5eOJuCyVIuAPC6w==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-body": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.13.0.tgz", - "integrity": "sha512-S4HgwAuO9dEsyX9sr6WBf9/xr+H2ASVaLn22aurJm1S2Lvc1wifLPYBQgFmNdCjaesTCNtOMUDpG+Rbnavyaqg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.14.1.tgz", + "integrity": "sha512-YpXcK3o2o1U+fhI8f60xahrhXuHmav6BZez9vIN3ZEJOxPFSr+qgr1cT2iyFz50L5+ZsLIVj2ZY+ALQjdsg8ig==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-button": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.13.0.tgz", - "integrity": "sha512-3y8IAHCCxh7ESHh1aOOqobZKUgyNxOKAGQ9TlJoyaLpsKUFzkN8nmrD0KXF0ADSuzvhMZ1CdRIJuZ5mjv2TwWQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.14.1.tgz", + "integrity": "sha512-V1Tl1vQ3lXYvvqHJHvGcc8URr7V1l/ZOsv7iLV4QRrh7kjKBXaRS7uUJtz6/PzEbNsGQCiNtXrODqcijLWlgaw==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-carousel": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.13.0.tgz", - "integrity": "sha512-ORSY5bEYlMlrWSIKI/lN0Tz3uGltWAjG8DQl2Yr3pwjwOaIzGE+kozrDf+T9xItfiIIbvKajef1dg7B7XgP0zg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.14.1.tgz", + "integrity": "sha512-Ku3MUWPk/TwHxVgKEUtzspy/ePaWtN/3z6/qvNik0KIn0ZUIZ4zvR2JtaVL5nd30LHSmUaNj30XMPkCjYiKkFA==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-cli": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.13.0.tgz", - "integrity": "sha512-kAZxpH0QqlTF/CcLzELgKw1ljKRxrmWJ310CJQhbPAxHvwQ/nIb+q82U+zRJAelRPPKjnOb+hSrMRqTgk9rH3w==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.14.1.tgz", + "integrity": "sha512-Gy6MnSygFXs0U1qOXTHqBg2vZX2VL/fAacgQzD4MHq4OuybWaTNSzXRwxBXYCxT3IJB874n2Q0Mxp+Xka+tnZg==", "dependencies": { "@babel/runtime": "^7.14.6", "chokidar": "^3.0.0", @@ -8885,9 +9120,9 @@ "html-minifier": "^4.0.0", "js-beautify": "^1.6.14", "lodash": "^4.17.21", - "mjml-core": "4.13.0", - "mjml-migrate": "4.13.0", - "mjml-parser-xml": "4.13.0", + "mjml-core": "4.14.1", + "mjml-migrate": "4.14.1", + "mjml-parser-xml": "4.14.1", "mjml-validator": "4.13.0", "yargs": "^16.1.0" }, @@ -8931,162 +9166,162 @@ } }, "node_modules/mjml-column": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.13.0.tgz", - "integrity": "sha512-O8FrWKK/bCy9XpKxrKRYWNdgWNaVd4TK4RqMeVI/I70IbnYnc1uf15jnsPMxCBSbT+NyXyk8k7fn099797uwpw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.14.1.tgz", + "integrity": "sha512-iixVCIX1YJtpQuwG2WbDr7FqofQrlTtGQ4+YAZXGiLThs0En3xNIJFQX9xJ8sgLEGGltyooHiNICBRlzSp9fDg==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-core": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.13.0.tgz", - "integrity": "sha512-kU5AoVTlZaXR/EDi3ix66xpzUe+kScYus71lBH/wo/B+LZW70GHE1AYWtsog5oJp1MuTHpMFTNuBD/wePeEgWg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.14.1.tgz", + "integrity": "sha512-di88rSfX+8r4r+cEqlQCO7CRM4mYZrfe2wSCu2je38i+ujjkLpF72cgLnjBlSG5aOUCZgYvlsZ85stqIz9LQfA==", "dependencies": { "@babel/runtime": "^7.14.6", - "cheerio": "1.0.0-rc.10", - "detect-node": "2.0.4", + "cheerio": "1.0.0-rc.12", + "detect-node": "^2.0.4", "html-minifier": "^4.0.0", "js-beautify": "^1.6.14", - "juice": "^7.0.0", + "juice": "^9.0.0", "lodash": "^4.17.21", - "mjml-migrate": "4.13.0", - "mjml-parser-xml": "4.13.0", + "mjml-migrate": "4.14.1", + "mjml-parser-xml": "4.14.1", "mjml-validator": "4.13.0" } }, "node_modules/mjml-divider": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.13.0.tgz", - "integrity": "sha512-ooPCwfmxEC+wJduqObYezMp7W5UCHjL9Y1LPB5FGna2FrOejgfd6Ix3ij8Wrmycmlol7E2N4D7c5NDH5DbRCJg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.14.1.tgz", + "integrity": "sha512-agqWY0aW2xaMiUOhYKDvcAAfOLalpbbtjKZAl1vWmNkURaoK4L7MgDilKHSJDFUlHGm2ZOArTrq8i6K0iyThBQ==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-group": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.13.0.tgz", - "integrity": "sha512-U7E8m8aaoAE/dMqjqXPjjrKcwO36B4cquAy9ASldECrIZJBcpFYO6eYf5yLXrNCUM2P0id8pgVjrUq23s00L7Q==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.14.1.tgz", + "integrity": "sha512-dJt5batgEJ7wxlxzqOfHOI94ABX+8DZBvAlHuddYO4CsLFHYv6XRIArLAMMnAKU76r6p3X8JxYeOjKZXdv49kg==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-head": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.13.0.tgz", - "integrity": "sha512-sL2qQuoVALXBCiemu4DPo9geDr8DuUdXVJxm+4nd6k5jpLCfSDmFlNhgSsLPzsYn7VEac3/sxsjLtomQ+6/BHg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.14.1.tgz", + "integrity": "sha512-KoCbtSeTAhx05Ugn9TB2UYt5sQinSCb7RGRer5iPQ3CrXj8hT5B5Svn6qvf/GACPkWl4auExHQh+XgLB+r3OEA==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-head-attributes": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.13.0.tgz", - "integrity": "sha512-haggCafno+0lQylxJStkINCVCPMwfTpwE6yjCHeGOpQl/TkoNmjNkDr7DEEbNTZbt4Ekg070lQFn7clDy38EoA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.14.1.tgz", + "integrity": "sha512-XdUNOp2csK28kBDSistInOyzWNwmu5HDNr4y1Z7vSQ1PfkmiuS6jWG7jHUjdoMhs27e6Leuyyc6a8gWSpqSWrg==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-head-breakpoint": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.13.0.tgz", - "integrity": "sha512-D2iPDeUKQK1+rYSNa2HGOvgfPxZhNyndTG0iBEb/FxdGge2hbeDCZEN0mwDYE3wWB+qSBqlCuMI+Vr4pEjZbKg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.14.1.tgz", + "integrity": "sha512-Qw9l/W/I5Z9p7I4ShgnEpAL9if4472ejcznbBnp+4Gq+sZoPa7iYoEPsa9UCGutlaCh3N3tIi2qKhl9qD8DFxA==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-head-font": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.13.0.tgz", - "integrity": "sha512-mYn8aWnbrEap5vX2b4662hkUv6WifcYzYn++Yi6OHrJQi55LpzcU+myAGpfQEXXrpU8vGwExMTFKsJq5n2Kaow==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.14.1.tgz", + "integrity": "sha512-oBYm1gaOdEMjE5BoZouRRD4lCNZ1jcpz92NR/F7xDyMaKCGN6T/+r4S5dq1gOLm9zWqClRHaECdFJNEmrDpZqA==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-head-html-attributes": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.13.0.tgz", - "integrity": "sha512-m30Oro297+18Zou/1qYjagtmCOWtYXeoS38OABQ5zOSzMItE3TcZI9JNcOueIIWIyFCETe8StrTAKcQ2GHwsDw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.14.1.tgz", + "integrity": "sha512-vlJsJc1Sm4Ml2XvLmp01zsdmWmzm6+jNCO7X3eYi9ngEh8LjMCLIQOncnOgjqm9uGpQu2EgUhwvYFZP2luJOVg==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-head-preview": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.13.0.tgz", - "integrity": "sha512-v0K/NocjFCbaoF/0IMVNmiqov91HxqT07vNTEl0Bt9lKFrTKVC01m1S4K7AB78T/bEeJ/HwmNjr1+TMtVNGGow==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.14.1.tgz", + "integrity": "sha512-89gQtt3fhl2dkYpHLF5HDQXz/RLpzecU6wmAIT7Dz6etjLGE1dgq2Ay6Bu/OeHjDcT1gbM131zvBwuXw8OydNw==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-head-style": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.13.0.tgz", - "integrity": "sha512-tBa33GL9Atn5bAM2UwE+uxv4rI29WgX/e5lXX+5GWlsb4thmiN6rxpFTNqBqWbBNRbZk4UEZF78M7Da8xC1ZGQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.14.1.tgz", + "integrity": "sha512-XryOuf32EDuUCBT2k99C1+H87IOM919oY6IqxKFJCDkmsbywKIum7ibhweJdcxiYGONKTC6xjuibGD3fQTTYNQ==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-head-title": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.13.0.tgz", - "integrity": "sha512-Mq0bjuZXJlwxfVcjuYihQcigZSDTKeQaG3nORR1D0jsOH2BXU4XgUK1UOcTXn2qCBIfRoIMq7rfzYs+L0CRhdw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.14.1.tgz", + "integrity": "sha512-aIfpmlQdf1eJZSSrFodmlC4g5GudBti2eMyG42M7/3NeLM6anEWoe+UkF/6OG4Zy0tCQ40BDJ5iBZlMsjQICzw==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-hero": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.13.0.tgz", - "integrity": "sha512-aWEOScdrhyjwdKBWG4XQaElRHP8LU5PtktkpMeBXa4yxrxNs25qRnDqMNkjSrnnmFKWZmQ166tfboY6RBNf0UA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.14.1.tgz", + "integrity": "sha512-TQJ3yfjrKYGkdEWjHLHhL99u/meKFYgnfJvlo9xeBvRjSM696jIjdqaPHaunfw4CP6d2OpCIMuacgOsvqQMWOA==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-image": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.13.0.tgz", - "integrity": "sha512-agMmm2wRZTIrKwrUnYFlnAbtrKYSP0R2en+Vf92HPspAwmaw3/AeOW/QxmSiMhfGf+xsEJyzVvR/nd33jbT3sg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.14.1.tgz", + "integrity": "sha512-jfKLPHXuFq83okwlNM1Um/AEWeVDgs2JXIOsWp2TtvXosnRvGGMzA5stKLYdy1x6UfKF4c1ovpMS162aYGp+xQ==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-migrate": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.13.0.tgz", - "integrity": "sha512-I1euHiAyNpaz+B5vH+Z4T+hg/YtI5p3PqQ3/zTLv8gi24V6BILjTaftWhH5+3R/gQkQhH0NUaWNnRmds+Mq5DQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.14.1.tgz", + "integrity": "sha512-d+9HKQOhZi3ZFAaFSDdjzJX9eDQGjMf3BArLWNm2okC4ZgfJSpOc77kgCyFV8ugvwc8fFegPnSV60Jl4xtvK2A==", "dependencies": { "@babel/runtime": "^7.14.6", "js-beautify": "^1.6.14", "lodash": "^4.17.21", - "mjml-core": "4.13.0", - "mjml-parser-xml": "4.13.0", + "mjml-core": "4.14.1", + "mjml-parser-xml": "4.14.1", "yargs": "^16.1.0" }, "bin": { @@ -9129,142 +9364,122 @@ } }, "node_modules/mjml-navbar": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.13.0.tgz", - "integrity": "sha512-0Oqyyk+OdtXfsjswRb/7Ql1UOjN4MbqFPKoyltJqtj+11MRpF5+Wjd74Dj9H7l81GFwkIB9OaP+ZMiD+TPECgg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.14.1.tgz", + "integrity": "sha512-rNy1Kw8CR3WQ+M55PFBAUDz2VEOjz+sk06OFnsnmNjoMVCjo1EV7OFLDAkmxAwqkC8h4zQWEOFY0MBqqoAg7+A==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-parser-xml": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.13.0.tgz", - "integrity": "sha512-phljtI8DaW++q0aybR/Ykv9zCyP/jCFypxVNo26r2IQo//VYXyc7JuLZZT8N/LAI8lZcwbTVxQPBzJTmZ5IfwQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.14.1.tgz", + "integrity": "sha512-9WQVeukbXfq9DUcZ8wOsHC6BTdhaVwTAJDYMIQglXLwKwN7I4pTCguDDHy5d0kbbzK5OCVxCdZe+bfVI6XANOQ==", "dependencies": { "@babel/runtime": "^7.14.6", "detect-node": "2.0.4", - "htmlparser2": "^4.1.0", + "htmlparser2": "^8.0.1", "lodash": "^4.17.15" } }, - "node_modules/mjml-parser-xml/node_modules/domhandler": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", - "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", - "dependencies": { - "domelementtype": "^2.0.1" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/mjml-parser-xml/node_modules/htmlparser2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", - "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^3.0.0", - "domutils": "^2.0.0", - "entities": "^2.0.0" - } + "node_modules/mjml-parser-xml/node_modules/detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" }, "node_modules/mjml-preset-core": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.13.0.tgz", - "integrity": "sha512-gxzYaKkvUrHuzT1oqjEPSDtdmgEnN99Hf5f1r2CR5aMOB1x66EA3T8ATvF1o7qrBTVV4KMVlQem3IubMSYJZRw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.14.1.tgz", + "integrity": "sha512-uUCqK9Z9d39rwB/+JDV2KWSZGB46W7rPQpc9Xnw1DRP7wD7qAfJwK6AZFCwfTgWdSxw0PwquVNcrUS9yBa9uhw==", "dependencies": { "@babel/runtime": "^7.14.6", - "mjml-accordion": "4.13.0", - "mjml-body": "4.13.0", - "mjml-button": "4.13.0", - "mjml-carousel": "4.13.0", - "mjml-column": "4.13.0", - "mjml-divider": "4.13.0", - "mjml-group": "4.13.0", - "mjml-head": "4.13.0", - "mjml-head-attributes": "4.13.0", - "mjml-head-breakpoint": "4.13.0", - "mjml-head-font": "4.13.0", - "mjml-head-html-attributes": "4.13.0", - "mjml-head-preview": "4.13.0", - "mjml-head-style": "4.13.0", - "mjml-head-title": "4.13.0", - "mjml-hero": "4.13.0", - "mjml-image": "4.13.0", - "mjml-navbar": "4.13.0", - "mjml-raw": "4.13.0", - "mjml-section": "4.13.0", - "mjml-social": "4.13.0", - "mjml-spacer": "4.13.0", - "mjml-table": "4.13.0", - "mjml-text": "4.13.0", - "mjml-wrapper": "4.13.0" + "mjml-accordion": "4.14.1", + "mjml-body": "4.14.1", + "mjml-button": "4.14.1", + "mjml-carousel": "4.14.1", + "mjml-column": "4.14.1", + "mjml-divider": "4.14.1", + "mjml-group": "4.14.1", + "mjml-head": "4.14.1", + "mjml-head-attributes": "4.14.1", + "mjml-head-breakpoint": "4.14.1", + "mjml-head-font": "4.14.1", + "mjml-head-html-attributes": "4.14.1", + "mjml-head-preview": "4.14.1", + "mjml-head-style": "4.14.1", + "mjml-head-title": "4.14.1", + "mjml-hero": "4.14.1", + "mjml-image": "4.14.1", + "mjml-navbar": "4.14.1", + "mjml-raw": "4.14.1", + "mjml-section": "4.14.1", + "mjml-social": "4.14.1", + "mjml-spacer": "4.14.1", + "mjml-table": "4.14.1", + "mjml-text": "4.14.1", + "mjml-wrapper": "4.14.1" } }, "node_modules/mjml-raw": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.13.0.tgz", - "integrity": "sha512-JbBYxwX1a/zbqnCrlDCRNqov2xqUrMCaEdTHfqE2athj479aQXvLKFM20LilTMaClp/dR0yfvFLfFVrC5ej4FQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.14.1.tgz", + "integrity": "sha512-9+4wzoXnCtfV6QPmjfJkZ50hxFB4Z8QZnl2Ac0D1Cn3dUF46UkmO5NLMu7UDIlm5DdFyycZrMOwvZS4wv9ksPw==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-section": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.13.0.tgz", - "integrity": "sha512-BLcqlhavtRakKtzDQPLv6Ae4Jt4imYWq/P0jo+Sjk7tP4QifgVA2KEQOirPK5ZUqw/lvK7Afhcths5rXZ2ItnQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.14.1.tgz", + "integrity": "sha512-Ik5pTUhpT3DOfB3hEmAWp8rZ0ilWtIivnL8XdUJRfgYE9D+MCRn+reIO+DAoJHxiQoI6gyeKkIP4B9OrQ7cHQw==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-social": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.13.0.tgz", - "integrity": "sha512-zL2a7Wwsk8OXF0Bqu+1B3La1UPwdTMcEXptO8zdh2V5LL6Xb7Gfyvx6w0CmmBtG5IjyCtqaKy5wtrcpG9Hvjfg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.14.1.tgz", + "integrity": "sha512-G44aOZXgZHukirjkeQWTTV36UywtE2YvSwWGNfo/8d+k5JdJJhCIrlwaahyKEAyH63G1B0Zt8b2lEWx0jigYUw==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-spacer": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.13.0.tgz", - "integrity": "sha512-Acw4QJ0MJ38W4IewXuMX7hLaW1BZaln+gEEuTfrv0xwPdTxX1ILqz4r+s9mYMxYkIDLWMCjBvXyQK6aWlid13A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.14.1.tgz", + "integrity": "sha512-5SfQCXTd3JBgRH1pUy6NVZ0lXBiRqFJPVHBdtC3OFvUS3q1w16eaAXlIUWMKTfy8CKhQrCiE6m65kc662ZpYxA==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-table": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.13.0.tgz", - "integrity": "sha512-UAWPVMaGReQhf776DFdiwdcJTIHTek3zzQ1pb+E7VlypEYgIpFvdUJ39UIiiflhqtdBATmHwKBOtePwU0MzFMg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.14.1.tgz", + "integrity": "sha512-aVBdX3WpyKVGh/PZNn2KgRem+PQhWlvnD00DKxDejRBsBSKYSwZ0t3EfFvZOoJ9DzfHsN0dHuwd6Z18Ps44NFQ==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-text": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.13.0.tgz", - "integrity": "sha512-uDuraaQFdu+6xfuigCimbeznnOnJfwRdcCL1lTBTusTuEvW/5Va6m2D3mnMeEpl+bp4+cxesXIz9st6A9pcg5A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.14.1.tgz", + "integrity": "sha512-yZuvf5z6qUxEo5CqOhCUltJlR6oySKVcQNHwoV5sneMaKdmBiaU4VDnlYFera9gMD9o3KBHIX6kUg7EHnCwBRQ==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "node_modules/mjml-validator": { @@ -9276,14 +9491,14 @@ } }, "node_modules/mjml-wrapper": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.13.0.tgz", - "integrity": "sha512-p/44JvHg04rAFR7QDImg8nZucEokIjFH6KJMHxsO0frJtLZ+IuakctzlZAADHsqiR52BwocDsXSa+o9SE2l6Ng==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.14.1.tgz", + "integrity": "sha512-aA5Xlq6d0hZ5LY+RvSaBqmVcLkvPvdhyAv3vQf3G41Gfhel4oIPmkLnVpHselWhV14A0KwIOIAKVxHtSAxyOTQ==", "dependencies": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0", - "mjml-section": "4.13.0" + "mjml-core": "4.14.1", + "mjml-section": "4.14.1" } }, "node_modules/mkdirp": { @@ -9413,9 +9628,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -10109,16 +10324,51 @@ } }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } }, "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dependencies": { - "parse5": "^6.0.1" + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/parseurl": { @@ -11020,7 +11270,9 @@ "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true, + "optional": true }, "node_modules/pump": { "version": "3.0.0", @@ -11317,9 +11569,9 @@ "dev": true }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regex-not": { "version": "1.0.2", @@ -11743,6 +11995,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, "bin": { "semver": "bin/semver" } @@ -12042,11 +12295,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==" - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -13580,11 +13828,6 @@ "node": ">=0.8.0" } }, - "node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" - }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -14172,13 +14415,13 @@ } }, "node_modules/web-resource-inliner": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-5.0.0.tgz", - "integrity": "sha512-AIihwH+ZmdHfkJm7BjSXiEClVt4zUFqX4YlFAzjL13wLtDuUneSaFvDBTbdYRecs35SiU7iNKbMnN+++wVfb6A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", + "integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==", "dependencies": { "ansi-colors": "^4.1.1", "escape-goat": "^3.0.0", - "htmlparser2": "^4.0.0", + "htmlparser2": "^5.0.0", "mime": "^2.4.6", "node-fetch": "^2.6.0", "valid-data-url": "^3.0.0" @@ -14210,14 +14453,17 @@ } }, "node_modules/web-resource-inliner/node_modules/htmlparser2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", - "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", "dependencies": { "domelementtype": "^2.0.1", - "domhandler": "^3.0.0", - "domutils": "^2.0.0", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/fb55/htmlparser2?sponsor=1" } }, "node_modules/web-resource-inliner/node_modules/mime": { @@ -14387,7 +14633,9 @@ "node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true, + "optional": true }, "node_modules/yaml": { "version": "1.10.2", @@ -14439,11 +14687,11 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" } }, "@foliojs-fork/fontkit": { @@ -14641,6 +14889,11 @@ "fastq": "^1.6.0" } }, + "@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + }, "@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", @@ -15925,29 +16178,112 @@ } }, "cheerio": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", - "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", "requires": { - "cheerio-select": "^1.5.0", - "dom-serializer": "^1.3.2", - "domhandler": "^4.2.0", - "htmlparser2": "^6.1.0", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "tslib": "^2.2.0" + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "dependencies": { + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + } } }, "cheerio-select": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", - "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "requires": { - "css-select": "^4.3.0", - "css-what": "^6.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.3.1", - "domutils": "^2.8.0" + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "dependencies": { + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + } } }, "chokidar": { @@ -16403,6 +16739,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, "requires": { "boolbase": "^1.0.0", "css-what": "^6.0.1", @@ -17090,9 +17427,9 @@ "dev": true }, "detect-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "dev-ip": { "version": "1.0.1", @@ -17263,20 +17600,57 @@ } }, "editorconfig": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", - "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", "requires": { - "commander": "^2.19.0", - "lru-cache": "^4.1.5", - "semver": "^5.6.0", - "sigmund": "^1.0.1" + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" }, "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -19641,14 +20015,49 @@ } }, "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + }, + "dependencies": { + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + } } }, "http-cache-semantics": { @@ -20551,13 +20960,13 @@ "integrity": "sha512-BVpRacWCbNfo/ALWxnLkIY/WRa4Ydg/LtwzIJZvDm7vrhV8Txv+ACi6EGnU11zT19sTc3KEPathWx6CtjWLD1w==" }, "js-beautify": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.7.tgz", - "integrity": "sha512-5SOX1KXPFKx+5f6ZrPsIPEY7NwKeQz47n3jm2i+XeHx9MoRsfQenlOP13FQhWvg8JRS0+XLO6XYUQ2GX+q+T9A==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.9.tgz", + "integrity": "sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==", "requires": { "config-chain": "^1.1.13", - "editorconfig": "^0.15.3", - "glob": "^8.0.3", + "editorconfig": "^1.0.3", + "glob": "^8.1.0", "nopt": "^6.0.0" }, "dependencies": { @@ -20570,9 +20979,9 @@ } }, "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -20582,9 +20991,9 @@ } }, "minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "requires": { "brace-expansion": "^2.0.1" } @@ -20658,21 +21067,21 @@ } }, "juice": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/juice/-/juice-7.0.0.tgz", - "integrity": "sha512-AjKQX31KKN+uJs+zaf+GW8mBO/f/0NqSh2moTMyvwBY+4/lXIYTU8D8I2h6BAV3Xnz6GGsbalUyFqbYMe+Vh+Q==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/juice/-/juice-9.1.0.tgz", + "integrity": "sha512-odblShmPrUoHUwRuC8EmLji5bPP2MLO1GL+gt4XU3tT2ECmbSrrMjtMQaqg3wgMFP2zvUzdPZGfxc5Trk3Z+fQ==", "requires": { - "cheerio": "^1.0.0-rc.3", - "commander": "^5.1.0", + "cheerio": "^1.0.0-rc.12", + "commander": "^6.1.0", "mensch": "^0.3.4", "slick": "^1.12.2", - "web-resource-inliner": "^5.0.0" + "web-resource-inliner": "^6.0.1" }, "dependencies": { "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" } } }, @@ -21022,6 +21431,8 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "optional": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -21430,62 +21841,62 @@ } }, "mjml": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.13.0.tgz", - "integrity": "sha512-OnFKESouLshz8DPFSb6M/dE8GkhiJnoy6LAam5TiLA1anAj24yQ2ZH388LtQoEkvTisqwiTmc9ejDh5ctnFaJQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.14.1.tgz", + "integrity": "sha512-f/wnWWIVbeb/ge3ff7c/KYYizI13QbGIp03odwwkCThsJsacw4gpZZAU7V4gXY3HxSXP2/q3jxOfaHVbkfNpOQ==", "requires": { "@babel/runtime": "^7.14.6", - "mjml-cli": "4.13.0", - "mjml-core": "4.13.0", - "mjml-migrate": "4.13.0", - "mjml-preset-core": "4.13.0", + "mjml-cli": "4.14.1", + "mjml-core": "4.14.1", + "mjml-migrate": "4.14.1", + "mjml-preset-core": "4.14.1", "mjml-validator": "4.13.0" } }, "mjml-accordion": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.13.0.tgz", - "integrity": "sha512-E3yihZW5Oq2p+sWOcr8kWeRTROmiTYOGxB4IOxW/jTycdY07N3FX3e6vuh7Fv3rryHEUaydUQYto3ICVyctI7w==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.14.1.tgz", + "integrity": "sha512-dpNXyjnhYwhM75JSjD4wFUa9JgHm86M2pa0CoTzdv1zOQz67ilc4BoK5mc2S0gOjJpjBShM5eOJuCyVIuAPC6w==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-body": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.13.0.tgz", - "integrity": "sha512-S4HgwAuO9dEsyX9sr6WBf9/xr+H2ASVaLn22aurJm1S2Lvc1wifLPYBQgFmNdCjaesTCNtOMUDpG+Rbnavyaqg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.14.1.tgz", + "integrity": "sha512-YpXcK3o2o1U+fhI8f60xahrhXuHmav6BZez9vIN3ZEJOxPFSr+qgr1cT2iyFz50L5+ZsLIVj2ZY+ALQjdsg8ig==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-button": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.13.0.tgz", - "integrity": "sha512-3y8IAHCCxh7ESHh1aOOqobZKUgyNxOKAGQ9TlJoyaLpsKUFzkN8nmrD0KXF0ADSuzvhMZ1CdRIJuZ5mjv2TwWQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.14.1.tgz", + "integrity": "sha512-V1Tl1vQ3lXYvvqHJHvGcc8URr7V1l/ZOsv7iLV4QRrh7kjKBXaRS7uUJtz6/PzEbNsGQCiNtXrODqcijLWlgaw==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-carousel": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.13.0.tgz", - "integrity": "sha512-ORSY5bEYlMlrWSIKI/lN0Tz3uGltWAjG8DQl2Yr3pwjwOaIzGE+kozrDf+T9xItfiIIbvKajef1dg7B7XgP0zg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.14.1.tgz", + "integrity": "sha512-Ku3MUWPk/TwHxVgKEUtzspy/ePaWtN/3z6/qvNik0KIn0ZUIZ4zvR2JtaVL5nd30LHSmUaNj30XMPkCjYiKkFA==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-cli": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.13.0.tgz", - "integrity": "sha512-kAZxpH0QqlTF/CcLzELgKw1ljKRxrmWJ310CJQhbPAxHvwQ/nIb+q82U+zRJAelRPPKjnOb+hSrMRqTgk9rH3w==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.14.1.tgz", + "integrity": "sha512-Gy6MnSygFXs0U1qOXTHqBg2vZX2VL/fAacgQzD4MHq4OuybWaTNSzXRwxBXYCxT3IJB874n2Q0Mxp+Xka+tnZg==", "requires": { "@babel/runtime": "^7.14.6", "chokidar": "^3.0.0", @@ -21493,9 +21904,9 @@ "html-minifier": "^4.0.0", "js-beautify": "^1.6.14", "lodash": "^4.17.21", - "mjml-core": "4.13.0", - "mjml-migrate": "4.13.0", - "mjml-parser-xml": "4.13.0", + "mjml-core": "4.14.1", + "mjml-migrate": "4.14.1", + "mjml-parser-xml": "4.14.1", "mjml-validator": "4.13.0", "yargs": "^16.1.0" }, @@ -21532,162 +21943,162 @@ } }, "mjml-column": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.13.0.tgz", - "integrity": "sha512-O8FrWKK/bCy9XpKxrKRYWNdgWNaVd4TK4RqMeVI/I70IbnYnc1uf15jnsPMxCBSbT+NyXyk8k7fn099797uwpw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.14.1.tgz", + "integrity": "sha512-iixVCIX1YJtpQuwG2WbDr7FqofQrlTtGQ4+YAZXGiLThs0En3xNIJFQX9xJ8sgLEGGltyooHiNICBRlzSp9fDg==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-core": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.13.0.tgz", - "integrity": "sha512-kU5AoVTlZaXR/EDi3ix66xpzUe+kScYus71lBH/wo/B+LZW70GHE1AYWtsog5oJp1MuTHpMFTNuBD/wePeEgWg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.14.1.tgz", + "integrity": "sha512-di88rSfX+8r4r+cEqlQCO7CRM4mYZrfe2wSCu2je38i+ujjkLpF72cgLnjBlSG5aOUCZgYvlsZ85stqIz9LQfA==", "requires": { "@babel/runtime": "^7.14.6", - "cheerio": "1.0.0-rc.10", - "detect-node": "2.0.4", + "cheerio": "1.0.0-rc.12", + "detect-node": "^2.0.4", "html-minifier": "^4.0.0", "js-beautify": "^1.6.14", - "juice": "^7.0.0", + "juice": "^9.0.0", "lodash": "^4.17.21", - "mjml-migrate": "4.13.0", - "mjml-parser-xml": "4.13.0", + "mjml-migrate": "4.14.1", + "mjml-parser-xml": "4.14.1", "mjml-validator": "4.13.0" } }, "mjml-divider": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.13.0.tgz", - "integrity": "sha512-ooPCwfmxEC+wJduqObYezMp7W5UCHjL9Y1LPB5FGna2FrOejgfd6Ix3ij8Wrmycmlol7E2N4D7c5NDH5DbRCJg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.14.1.tgz", + "integrity": "sha512-agqWY0aW2xaMiUOhYKDvcAAfOLalpbbtjKZAl1vWmNkURaoK4L7MgDilKHSJDFUlHGm2ZOArTrq8i6K0iyThBQ==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-group": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.13.0.tgz", - "integrity": "sha512-U7E8m8aaoAE/dMqjqXPjjrKcwO36B4cquAy9ASldECrIZJBcpFYO6eYf5yLXrNCUM2P0id8pgVjrUq23s00L7Q==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.14.1.tgz", + "integrity": "sha512-dJt5batgEJ7wxlxzqOfHOI94ABX+8DZBvAlHuddYO4CsLFHYv6XRIArLAMMnAKU76r6p3X8JxYeOjKZXdv49kg==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-head": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.13.0.tgz", - "integrity": "sha512-sL2qQuoVALXBCiemu4DPo9geDr8DuUdXVJxm+4nd6k5jpLCfSDmFlNhgSsLPzsYn7VEac3/sxsjLtomQ+6/BHg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.14.1.tgz", + "integrity": "sha512-KoCbtSeTAhx05Ugn9TB2UYt5sQinSCb7RGRer5iPQ3CrXj8hT5B5Svn6qvf/GACPkWl4auExHQh+XgLB+r3OEA==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-head-attributes": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.13.0.tgz", - "integrity": "sha512-haggCafno+0lQylxJStkINCVCPMwfTpwE6yjCHeGOpQl/TkoNmjNkDr7DEEbNTZbt4Ekg070lQFn7clDy38EoA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.14.1.tgz", + "integrity": "sha512-XdUNOp2csK28kBDSistInOyzWNwmu5HDNr4y1Z7vSQ1PfkmiuS6jWG7jHUjdoMhs27e6Leuyyc6a8gWSpqSWrg==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-head-breakpoint": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.13.0.tgz", - "integrity": "sha512-D2iPDeUKQK1+rYSNa2HGOvgfPxZhNyndTG0iBEb/FxdGge2hbeDCZEN0mwDYE3wWB+qSBqlCuMI+Vr4pEjZbKg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.14.1.tgz", + "integrity": "sha512-Qw9l/W/I5Z9p7I4ShgnEpAL9if4472ejcznbBnp+4Gq+sZoPa7iYoEPsa9UCGutlaCh3N3tIi2qKhl9qD8DFxA==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-head-font": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.13.0.tgz", - "integrity": "sha512-mYn8aWnbrEap5vX2b4662hkUv6WifcYzYn++Yi6OHrJQi55LpzcU+myAGpfQEXXrpU8vGwExMTFKsJq5n2Kaow==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.14.1.tgz", + "integrity": "sha512-oBYm1gaOdEMjE5BoZouRRD4lCNZ1jcpz92NR/F7xDyMaKCGN6T/+r4S5dq1gOLm9zWqClRHaECdFJNEmrDpZqA==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-head-html-attributes": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.13.0.tgz", - "integrity": "sha512-m30Oro297+18Zou/1qYjagtmCOWtYXeoS38OABQ5zOSzMItE3TcZI9JNcOueIIWIyFCETe8StrTAKcQ2GHwsDw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.14.1.tgz", + "integrity": "sha512-vlJsJc1Sm4Ml2XvLmp01zsdmWmzm6+jNCO7X3eYi9ngEh8LjMCLIQOncnOgjqm9uGpQu2EgUhwvYFZP2luJOVg==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-head-preview": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.13.0.tgz", - "integrity": "sha512-v0K/NocjFCbaoF/0IMVNmiqov91HxqT07vNTEl0Bt9lKFrTKVC01m1S4K7AB78T/bEeJ/HwmNjr1+TMtVNGGow==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.14.1.tgz", + "integrity": "sha512-89gQtt3fhl2dkYpHLF5HDQXz/RLpzecU6wmAIT7Dz6etjLGE1dgq2Ay6Bu/OeHjDcT1gbM131zvBwuXw8OydNw==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-head-style": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.13.0.tgz", - "integrity": "sha512-tBa33GL9Atn5bAM2UwE+uxv4rI29WgX/e5lXX+5GWlsb4thmiN6rxpFTNqBqWbBNRbZk4UEZF78M7Da8xC1ZGQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.14.1.tgz", + "integrity": "sha512-XryOuf32EDuUCBT2k99C1+H87IOM919oY6IqxKFJCDkmsbywKIum7ibhweJdcxiYGONKTC6xjuibGD3fQTTYNQ==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-head-title": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.13.0.tgz", - "integrity": "sha512-Mq0bjuZXJlwxfVcjuYihQcigZSDTKeQaG3nORR1D0jsOH2BXU4XgUK1UOcTXn2qCBIfRoIMq7rfzYs+L0CRhdw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.14.1.tgz", + "integrity": "sha512-aIfpmlQdf1eJZSSrFodmlC4g5GudBti2eMyG42M7/3NeLM6anEWoe+UkF/6OG4Zy0tCQ40BDJ5iBZlMsjQICzw==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-hero": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.13.0.tgz", - "integrity": "sha512-aWEOScdrhyjwdKBWG4XQaElRHP8LU5PtktkpMeBXa4yxrxNs25qRnDqMNkjSrnnmFKWZmQ166tfboY6RBNf0UA==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.14.1.tgz", + "integrity": "sha512-TQJ3yfjrKYGkdEWjHLHhL99u/meKFYgnfJvlo9xeBvRjSM696jIjdqaPHaunfw4CP6d2OpCIMuacgOsvqQMWOA==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-image": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.13.0.tgz", - "integrity": "sha512-agMmm2wRZTIrKwrUnYFlnAbtrKYSP0R2en+Vf92HPspAwmaw3/AeOW/QxmSiMhfGf+xsEJyzVvR/nd33jbT3sg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.14.1.tgz", + "integrity": "sha512-jfKLPHXuFq83okwlNM1Um/AEWeVDgs2JXIOsWp2TtvXosnRvGGMzA5stKLYdy1x6UfKF4c1ovpMS162aYGp+xQ==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-migrate": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.13.0.tgz", - "integrity": "sha512-I1euHiAyNpaz+B5vH+Z4T+hg/YtI5p3PqQ3/zTLv8gi24V6BILjTaftWhH5+3R/gQkQhH0NUaWNnRmds+Mq5DQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.14.1.tgz", + "integrity": "sha512-d+9HKQOhZi3ZFAaFSDdjzJX9eDQGjMf3BArLWNm2okC4ZgfJSpOc77kgCyFV8ugvwc8fFegPnSV60Jl4xtvK2A==", "requires": { "@babel/runtime": "^7.14.6", "js-beautify": "^1.6.14", "lodash": "^4.17.21", - "mjml-core": "4.13.0", - "mjml-parser-xml": "4.13.0", + "mjml-core": "4.14.1", + "mjml-parser-xml": "4.14.1", "yargs": "^16.1.0" }, "dependencies": { @@ -21723,138 +22134,124 @@ } }, "mjml-navbar": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.13.0.tgz", - "integrity": "sha512-0Oqyyk+OdtXfsjswRb/7Ql1UOjN4MbqFPKoyltJqtj+11MRpF5+Wjd74Dj9H7l81GFwkIB9OaP+ZMiD+TPECgg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.14.1.tgz", + "integrity": "sha512-rNy1Kw8CR3WQ+M55PFBAUDz2VEOjz+sk06OFnsnmNjoMVCjo1EV7OFLDAkmxAwqkC8h4zQWEOFY0MBqqoAg7+A==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-parser-xml": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.13.0.tgz", - "integrity": "sha512-phljtI8DaW++q0aybR/Ykv9zCyP/jCFypxVNo26r2IQo//VYXyc7JuLZZT8N/LAI8lZcwbTVxQPBzJTmZ5IfwQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.14.1.tgz", + "integrity": "sha512-9WQVeukbXfq9DUcZ8wOsHC6BTdhaVwTAJDYMIQglXLwKwN7I4pTCguDDHy5d0kbbzK5OCVxCdZe+bfVI6XANOQ==", "requires": { "@babel/runtime": "^7.14.6", "detect-node": "2.0.4", - "htmlparser2": "^4.1.0", + "htmlparser2": "^8.0.1", "lodash": "^4.17.15" }, "dependencies": { - "domhandler": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", - "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", - "requires": { - "domelementtype": "^2.0.1" - } - }, - "htmlparser2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", - "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^3.0.0", - "domutils": "^2.0.0", - "entities": "^2.0.0" - } + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" } } }, "mjml-preset-core": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.13.0.tgz", - "integrity": "sha512-gxzYaKkvUrHuzT1oqjEPSDtdmgEnN99Hf5f1r2CR5aMOB1x66EA3T8ATvF1o7qrBTVV4KMVlQem3IubMSYJZRw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.14.1.tgz", + "integrity": "sha512-uUCqK9Z9d39rwB/+JDV2KWSZGB46W7rPQpc9Xnw1DRP7wD7qAfJwK6AZFCwfTgWdSxw0PwquVNcrUS9yBa9uhw==", "requires": { "@babel/runtime": "^7.14.6", - "mjml-accordion": "4.13.0", - "mjml-body": "4.13.0", - "mjml-button": "4.13.0", - "mjml-carousel": "4.13.0", - "mjml-column": "4.13.0", - "mjml-divider": "4.13.0", - "mjml-group": "4.13.0", - "mjml-head": "4.13.0", - "mjml-head-attributes": "4.13.0", - "mjml-head-breakpoint": "4.13.0", - "mjml-head-font": "4.13.0", - "mjml-head-html-attributes": "4.13.0", - "mjml-head-preview": "4.13.0", - "mjml-head-style": "4.13.0", - "mjml-head-title": "4.13.0", - "mjml-hero": "4.13.0", - "mjml-image": "4.13.0", - "mjml-navbar": "4.13.0", - "mjml-raw": "4.13.0", - "mjml-section": "4.13.0", - "mjml-social": "4.13.0", - "mjml-spacer": "4.13.0", - "mjml-table": "4.13.0", - "mjml-text": "4.13.0", - "mjml-wrapper": "4.13.0" + "mjml-accordion": "4.14.1", + "mjml-body": "4.14.1", + "mjml-button": "4.14.1", + "mjml-carousel": "4.14.1", + "mjml-column": "4.14.1", + "mjml-divider": "4.14.1", + "mjml-group": "4.14.1", + "mjml-head": "4.14.1", + "mjml-head-attributes": "4.14.1", + "mjml-head-breakpoint": "4.14.1", + "mjml-head-font": "4.14.1", + "mjml-head-html-attributes": "4.14.1", + "mjml-head-preview": "4.14.1", + "mjml-head-style": "4.14.1", + "mjml-head-title": "4.14.1", + "mjml-hero": "4.14.1", + "mjml-image": "4.14.1", + "mjml-navbar": "4.14.1", + "mjml-raw": "4.14.1", + "mjml-section": "4.14.1", + "mjml-social": "4.14.1", + "mjml-spacer": "4.14.1", + "mjml-table": "4.14.1", + "mjml-text": "4.14.1", + "mjml-wrapper": "4.14.1" } }, "mjml-raw": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.13.0.tgz", - "integrity": "sha512-JbBYxwX1a/zbqnCrlDCRNqov2xqUrMCaEdTHfqE2athj479aQXvLKFM20LilTMaClp/dR0yfvFLfFVrC5ej4FQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.14.1.tgz", + "integrity": "sha512-9+4wzoXnCtfV6QPmjfJkZ50hxFB4Z8QZnl2Ac0D1Cn3dUF46UkmO5NLMu7UDIlm5DdFyycZrMOwvZS4wv9ksPw==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-section": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.13.0.tgz", - "integrity": "sha512-BLcqlhavtRakKtzDQPLv6Ae4Jt4imYWq/P0jo+Sjk7tP4QifgVA2KEQOirPK5ZUqw/lvK7Afhcths5rXZ2ItnQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.14.1.tgz", + "integrity": "sha512-Ik5pTUhpT3DOfB3hEmAWp8rZ0ilWtIivnL8XdUJRfgYE9D+MCRn+reIO+DAoJHxiQoI6gyeKkIP4B9OrQ7cHQw==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-social": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.13.0.tgz", - "integrity": "sha512-zL2a7Wwsk8OXF0Bqu+1B3La1UPwdTMcEXptO8zdh2V5LL6Xb7Gfyvx6w0CmmBtG5IjyCtqaKy5wtrcpG9Hvjfg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.14.1.tgz", + "integrity": "sha512-G44aOZXgZHukirjkeQWTTV36UywtE2YvSwWGNfo/8d+k5JdJJhCIrlwaahyKEAyH63G1B0Zt8b2lEWx0jigYUw==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-spacer": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.13.0.tgz", - "integrity": "sha512-Acw4QJ0MJ38W4IewXuMX7hLaW1BZaln+gEEuTfrv0xwPdTxX1ILqz4r+s9mYMxYkIDLWMCjBvXyQK6aWlid13A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.14.1.tgz", + "integrity": "sha512-5SfQCXTd3JBgRH1pUy6NVZ0lXBiRqFJPVHBdtC3OFvUS3q1w16eaAXlIUWMKTfy8CKhQrCiE6m65kc662ZpYxA==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-table": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.13.0.tgz", - "integrity": "sha512-UAWPVMaGReQhf776DFdiwdcJTIHTek3zzQ1pb+E7VlypEYgIpFvdUJ39UIiiflhqtdBATmHwKBOtePwU0MzFMg==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.14.1.tgz", + "integrity": "sha512-aVBdX3WpyKVGh/PZNn2KgRem+PQhWlvnD00DKxDejRBsBSKYSwZ0t3EfFvZOoJ9DzfHsN0dHuwd6Z18Ps44NFQ==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-text": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.13.0.tgz", - "integrity": "sha512-uDuraaQFdu+6xfuigCimbeznnOnJfwRdcCL1lTBTusTuEvW/5Va6m2D3mnMeEpl+bp4+cxesXIz9st6A9pcg5A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.14.1.tgz", + "integrity": "sha512-yZuvf5z6qUxEo5CqOhCUltJlR6oySKVcQNHwoV5sneMaKdmBiaU4VDnlYFera9gMD9o3KBHIX6kUg7EHnCwBRQ==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0" + "mjml-core": "4.14.1" } }, "mjml-validator": { @@ -21866,14 +22263,14 @@ } }, "mjml-wrapper": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.13.0.tgz", - "integrity": "sha512-p/44JvHg04rAFR7QDImg8nZucEokIjFH6KJMHxsO0frJtLZ+IuakctzlZAADHsqiR52BwocDsXSa+o9SE2l6Ng==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.14.1.tgz", + "integrity": "sha512-aA5Xlq6d0hZ5LY+RvSaBqmVcLkvPvdhyAv3vQf3G41Gfhel4oIPmkLnVpHselWhV14A0KwIOIAKVxHtSAxyOTQ==", "requires": { "@babel/runtime": "^7.14.6", "lodash": "^4.17.21", - "mjml-core": "4.13.0", - "mjml-section": "4.13.0" + "mjml-core": "4.14.1", + "mjml-section": "4.14.1" } }, "mkdirp": { @@ -21977,9 +22374,9 @@ } }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "requires": { "whatwg-url": "^5.0.0" } @@ -22492,16 +22889,37 @@ "dev": true }, "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + } + } }, "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", "requires": { - "parse5": "^6.0.1" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "dependencies": { + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + } } }, "parseurl": { @@ -23099,7 +23517,9 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true, + "optional": true }, "pump": { "version": "3.0.0", @@ -23344,9 +23764,9 @@ } }, "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "regex-not": { "version": "1.0.2", @@ -23663,7 +24083,8 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true }, "semver-greatest-satisfied-range": { "version": "1.1.0", @@ -23909,11 +24330,6 @@ "object-inspect": "^1.9.0" } }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==" - }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -25135,11 +25551,6 @@ } } }, - "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -25599,13 +26010,13 @@ } }, "web-resource-inliner": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-5.0.0.tgz", - "integrity": "sha512-AIihwH+ZmdHfkJm7BjSXiEClVt4zUFqX4YlFAzjL13wLtDuUneSaFvDBTbdYRecs35SiU7iNKbMnN+++wVfb6A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", + "integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==", "requires": { "ansi-colors": "^4.1.1", "escape-goat": "^3.0.0", - "htmlparser2": "^4.0.0", + "htmlparser2": "^5.0.0", "mime": "^2.4.6", "node-fetch": "^2.6.0", "valid-data-url": "^3.0.0" @@ -25625,13 +26036,13 @@ } }, "htmlparser2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", - "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", "requires": { "domelementtype": "^2.0.1", - "domhandler": "^3.0.0", - "domutils": "^2.0.0", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", "entities": "^2.0.0" } }, @@ -25754,7 +26165,9 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true, + "optional": true }, "yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index 6eef35c..51bde6f 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "jquery.easing": "^1.4.1", "jstree": "^3.3.12", "jszip": "^3.10.1", - "mjml": "^4.13.0", + "mjml": "^4.14.1", "pdfmake": "^0.2.7", "pg": "^8.8.0", "popper.js": "^1.16.1", diff --git a/requirements.txt b/requirements.txt index c1b500c..ea77c2d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1 @@ -# This file is expected by Heroku. - -r requirements/production.txt diff --git a/requirements/base.txt b/requirements/base.txt index ed86f2c..4d9a60d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,34 +1,32 @@ Fraction~=2.2.0 -Pillow~=9.5.0 # https://github.com/python-pillow/Pillow -argon2-cffi~=21.3.0 # https://github.com/hynek/argon2_cffi +Pillow~=10.1.0 # https://github.com/python-pillow/Pillow +argon2-cffi~=23.1.0 # https://github.com/hynek/argon2_cffi coveralls~=3.3.1 -django-allauth~=0.54.0 # https://github.com/pennersr/django-allauth -django-anymail[mailgun]~=9.1 # https://github.com/anymail/django-anymail -django-compressor~=4.3.1 # https://github.com/django-compressor/django-compressor -django-cors-headers~=3.14.0 # https://github.com/adamchainz/django-cors-headers -django-crispy-forms~=2.0 # https://github.com/django-crispy-forms/django-crispy-forms -django-environ~=0.10.0 # https://github.com/joke2k/django-environ +django-allauth~=0.58.0 # https://github.com/pennersr/django-allauth +django-anymail[mailgun]~=10.2 # https://github.com/anymail/django-anymail +django-compressor~=4.4 # https://github.com/django-compressor/django-compressor +django-cors-headers~=4.3.0 # https://github.com/adamchainz/django-cors-headers +django-crispy-forms~=2.1 # https://github.com/django-crispy-forms/django-crispy-forms +django-environ~=0.11.2 # https://github.com/joke2k/django-environ django-filter>=2.2,<3.0 # this constraint is required for wagtail django-mjml[requests]~=1.1 -django-taggit~=3.1.0 +django-taggit~=4.0.0 django-modelcluster~=6.0 django-model-utils~=4.3.0 # https://github.com/jazzband/django-model-utils django-oauth-toolkit~=2.2.0 django-phonenumber-field~=7.0.2 django-storages[google]~=1.13.2 # https://github.com/jschneier/django-storages djangorestframework-datatables~=0.7.0 -djangorestframework~=3.13.0 # https://github.com/encode/django-rest-framework +djangorestframework~=3.14.0 # https://github.com/encode/django-rest-framework django-rest-framework-braces~=0.3.4 drf-generators~=0.5.0 -django~=3.2 # pyup: < 3.2 # https://www.djangoproject.com/ +django~=4.2 # pyup: < 3.2 # https://www.djangoproject.com/ google-api-core~=2.11.0 google-api-python-client~=2.86.0 google-auth~=2.17.3 google-cloud-secret-manager~=2.16.1 google-cloud-storage~=2.8.0 gcloud~=0.18.3 -# Version 4.21.0 introduces breaking changes that cause django to fail -# It is used as a dependency in google packages protobuf~=4.22.3 openpyxl~=3.1.2 phonenumbers~=8.13.10 @@ -37,13 +35,11 @@ pytz~=2023.3 # https://github.com/stub42/pytz rcssmin~=1.1.1 # https://github.com/ndparker/rcssmin requests~=2.28.2 uvicorn[standard]~=0.21.1 # https://github.com/encode/uvicorn -whitenoise~=6.4.0 # https://github.com/evansd/whitenoise -wagtail~=4.1 +whitenoise~=6.6.0 # https://github.com/evansd/whitenoise +wagtail~=5.1 Wand~=0.6.11 opencv-python==4.7.0.72 rustface~=0.1.0 wagtailvideos~=4.2.0 -wagtailmedia~=0.13.0 -wagtail-readinglevel~=3.5.0 -wagtailfontawesome~=1.2.1 +wagtailmedia~=0.14.0 django-nested-admin==4.0.2 diff --git a/requirements/local.txt b/requirements/local.txt index 47d0a6e..5b2d332 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -1,44 +1,44 @@ -r base.txt -Werkzeug~=2.3.3 # https://github.com/pallets/werkzeug -ipdb~=0.13.9 # https://github.com/gotcha/ipdb -psycopg2-binary~=2.9.1 # https://github.com/psycopg/psycopg2 -watchgod~=0.8 # https://github.com/samuelcolvin/watchgod +Werkzeug~=3.0.1 # https://github.com/pallets/werkzeug +ipdb~=0.13.13 # https://github.com/gotcha/ipdb +psycopg2-binary~=2.9.9 # https://github.com/psycopg/psycopg2 +watchgod~=0.8.2 # https://github.com/samuelcolvin/watchgod # Testing # ------------------------------------------------------------------------------ mypy~=0.991 # https://github.com/python/mypy django-stubs~=1.9.0 # https://github.com/typeddjango/django-stubs -pytest~=7.3.1 # https://github.com/pytest-dev/pytest +pytest~=7.4.3 # https://github.com/pytest-dev/pytest pytest-sugar~=0.9.7 # https://github.com/Frozenball/pytest-sugar -pytest-cov~=4.0.0 -pytest-xdist~=2.4.0 -model_bakery~=1.11.0 +pytest-cov~=4.1.0 +pytest-xdist~=3.3.1 +model_bakery~=1.17.0 # Documentation # ------------------------------------------------------------------------------ -sphinx~=4.2.0 # https://github.com/sphinx-doc/sphinx +sphinx~=7.2.6 # https://github.com/sphinx-doc/sphinx sphinx-autobuild~=2021.3.14 # https://github.com/GaretJax/sphinx-autobuild # Code quality # ------------------------------------------------------------------------------ -flake8~=6.0.0 # https://github.com/PyCQA/flake8 -flake8-isort~=6.0.0 # https://github.com/gforcada/flake8-isort +flake8~=6.1.0 # https://github.com/PyCQA/flake8 +flake8-isort~=6.1.0 # https://github.com/gforcada/flake8-isort coverage~=6.5.0 # https://github.com/nedbat/coveragepy -black~=23.3.0 # https://github.com/psf/black -click==8.1.3 -pylint-django~=2.4.4 # https://github.com/PyCQA/pylint-django -pre-commit~=2.15.0 # https://github.com/pre-commit/pre-commit -Faker~=9.8.0 -pylint>=2.17.3 +black~=23.10.1 # https://github.com/psf/black +click==8.1.7 +pylint-django~=2.5.5 # https://github.com/PyCQA/pylint-django +pre-commit~=3.5.0 # https://github.com/pre-commit/pre-commit +Faker~=19.12.1 +pylint>=3.0.2 # Django # ------------------------------------------------------------------------------ -factory-boy~=3.2.0 # https://github.com/FactoryBoy/factory_boy -django-debug-toolbar~=3.2.1 # https://github.com/jazzband/django-debug-toolbar -django-extensions~=3.1.3 # https://github.com/django-extensions/django-extensions -django-coverage-plugin~=3.0.0 # https://github.com/nedbat/django_coverage_plugin -pytest-django~=4.4.0 # https://github.com/pytest-dev/pytest-django +factory-boy~=3.3.0 # https://github.com/FactoryBoy/factory_boy +django-debug-toolbar~=4.2.0 # https://github.com/jazzband/django-debug-toolbar +django-extensions~=3.2.3 # https://github.com/django-extensions/django-extensions +django-coverage-plugin~=3.1.0 # https://github.com/nedbat/django_coverage_plugin +pytest-django~=4.6.0 # https://github.com/pytest-dev/pytest-django # mypy type definitions types-pytz>=2021.1.2 @@ -50,4 +50,4 @@ types-futures>=0.1.6 types-protobuf>=3.17.4 types-setuptools>=57.0.2 types-toml>=0.1.5 -types_requests>=2.25.6 +types_requests>=2.30.0.0 diff --git a/requirements/production.txt b/requirements/production.txt index 184f2ec..c92b7fe 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -1,5 +1,6 @@ --r local.txt +-r base.txt -gunicorn~=20.1.0 # https://github.com/benoitc/gunicorn +elasticsearch~=8.10.1 +gunicorn~=21.2.0 # https://github.com/benoitc/gunicorn psycopg2-binary~=2.9.1 # https://github.com/psycopg/psycopg2 sentry-sdk~=1.5.12 # https://github.com/getsentry/sentry-python