diff --git a/poetry.lock b/poetry.lock index 374b8121..a06b22aa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1554,6 +1554,32 @@ files = [ {file = "rjsmin-1.2.1.tar.gz", hash = "sha256:1f982be8e011438777a94307279b40134a3935fc0f079312ee299725b8af5411"}, ] +[[package]] +name = "ruff" +version = "0.1.8" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7de792582f6e490ae6aef36a58d85df9f7a0cfd1b0d4fe6b4fb51803a3ac96fa"}, + {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8e3255afd186c142eef4ec400d7826134f028a85da2146102a1172ecc7c3696"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff78a7583020da124dd0deb835ece1d87bb91762d40c514ee9b67a087940528b"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd8ee69b02e7bdefe1e5da2d5b6eaaddcf4f90859f00281b2333c0e3a0cc9cd6"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a05b0ddd7ea25495e4115a43125e8a7ebed0aa043c3d432de7e7d6e8e8cd6448"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e6f08ca730f4dc1b76b473bdf30b1b37d42da379202a059eae54ec7fc1fbcfed"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f35960b02df6b827c1b903091bb14f4b003f6cf102705efc4ce78132a0aa5af3"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d076717c67b34c162da7c1a5bda16ffc205e0e0072c03745275e7eab888719f"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a21ab023124eafb7cef6d038f835cb1155cd5ea798edd8d9eb2f8b84be07d9"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ce697c463458555027dfb194cb96d26608abab920fa85213deb5edf26e026664"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db6cedd9ffed55548ab313ad718bc34582d394e27a7875b4b952c2d29c001b26"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:05ffe9dbd278965271252704eddb97b4384bf58b971054d517decfbf8c523f05"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5daaeaf00ae3c1efec9742ff294b06c3a2a9db8d3db51ee4851c12ad385cda30"}, + {file = "ruff-0.1.8-py3-none-win32.whl", hash = "sha256:e49fbdfe257fa41e5c9e13c79b9e79a23a79bd0e40b9314bc53840f520c2c0b3"}, + {file = "ruff-0.1.8-py3-none-win_amd64.whl", hash = "sha256:f41f692f1691ad87f51708b823af4bb2c5c87c9248ddd3191c8f088e66ce590a"}, + {file = "ruff-0.1.8-py3-none-win_arm64.whl", hash = "sha256:aa8ee4f8440023b0a6c3707f76cadce8657553655dcbb5fc9b2f9bb9bee389f6"}, + {file = "ruff-0.1.8.tar.gz", hash = "sha256:f7ee467677467526cfe135eab86a40a0e8db43117936ac4f9b469ce9cdb3fb62"}, +] + [[package]] name = "sentry-sdk" version = "1.39.1" @@ -1956,3 +1982,4 @@ files = [ lock-version = "2.0" python-versions = "^3.10" content-hash = "06d4b7c98b3c9683155df8f8bf0667eea7feed2bc59506236907f09aac013253" + diff --git a/pyproject.toml b/pyproject.toml index aca36e19..d2ae6a08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ django-debug-toolbar = "^4.2.0" django-import-export = "^3.3.4" factory-boy = "^3.3.0" sentry-sdk = {extras = ["django"], version = "^1.39.1"} +ruff = "^0.1.8" [tool.poetry.group.dev.dependencies] diff --git a/src/core/settings/base.py b/src/core/settings/base.py index 5c5117d4..b7717732 100644 --- a/src/core/settings/base.py +++ b/src/core/settings/base.py @@ -167,7 +167,8 @@ ] # Can use MailTrap SMTP Setup for now (dev and staging only). -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" + EMAIL_HOST = env("EMAIL_HOST",default="smtp.mailtrap.io") EMAIL_PORT = env("EMAIL_PORT",default=587) # 2525 diff --git a/src/rh/forms.py b/src/rh/forms.py index 91183433..abaae825 100644 --- a/src/rh/forms.py +++ b/src/rh/forms.py @@ -327,3 +327,38 @@ def __init__(self, *args, project, **kwargs): ) self.fields["donor"].queryset = self.fields["donor"].queryset.filter(pk__in=donor_ids) self.fields["budget_currency"].queryset = self.fields["budget_currency"].queryset.filter(pk=budget_currency.pk) + + +class OrganizationRegisterForm(forms.ModelForm): + """Organization Registeration Form""" + + class Meta: + model = Organization + fields = "__all__" + exclude = ("old_id",) + labels = {"clusters": "Clusters / Sectors", "name": "Organization Name"} + + widgets = { + "clusters": forms.SelectMultiple(attrs={"class": "js_multiselect"}), + "countries": forms.SelectMultiple(attrs={"class": "js_multiselect"}), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["countries"].queryset = self.fields["countries"].queryset.filter(type="Country") + + def clean_name(self): + """check if organization name already exits""" + name = self.cleaned_data.get("name") + org_name = Organization.objects.filter(name__iexact=name) + if org_name.exists(): + raise forms.ValidationError(f"{name} already exists...!") + return name + + def clean_code(self): + """check if organization code exists""" + code = self.cleaned_data.get("code") + org_code = Organization.objects.filter(code__iexact=code) + if org_code.exists(): + raise forms.ValidationError(f"{code} aleady exists...!") + return code diff --git a/src/rh/migrations/0005_remove_beneficiarytype_is_regular_beneficiary_and_more.py b/src/rh/migrations/0005_remove_beneficiarytype_is_regular_beneficiary_and_more.py index e283b979..238875dd 100644 --- a/src/rh/migrations/0005_remove_beneficiarytype_is_regular_beneficiary_and_more.py +++ b/src/rh/migrations/0005_remove_beneficiarytype_is_regular_beneficiary_and_more.py @@ -4,19 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('rh', '0004_rename_gran_type_indicator_grant_type'), + ("rh", "0004_rename_gran_type_indicator_grant_type"), ] operations = [ migrations.RemoveField( - model_name='beneficiarytype', - name='is_regular_beneficiary', + model_name="beneficiarytype", + name="is_regular_beneficiary", ), migrations.AlterField( - model_name='beneficiarytype', - name='is_hrp_beneficiary', + model_name="beneficiarytype", + name="is_hrp_beneficiary", field=models.BooleanField(default=False, null=True), ), ] diff --git a/src/rh/migrations/0006_alter_organization_type.py b/src/rh/migrations/0006_alter_organization_type.py new file mode 100644 index 00000000..04bfc4e2 --- /dev/null +++ b/src/rh/migrations/0006_alter_organization_type.py @@ -0,0 +1,27 @@ +# Generated by Django 4.0.6 on 2023-12-18 07:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("rh", "0005_remove_beneficiarytype_is_regular_beneficiary_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="organization", + name="type", + field=models.CharField( + blank=True, + choices=[ + ("National NGO", "National NGO"), + ("International NGO", "International NGO"), + ("Government", "Government"), + ("Business", "Business"), + ], + max_length=200, + null=True, + ), + ), + ] diff --git a/src/rh/models.py b/src/rh/models.py index 3a0c8b8f..a8232657 100644 --- a/src/rh/models.py +++ b/src/rh/models.py @@ -72,7 +72,7 @@ class BeneficiaryType(models.Model): on_delete=models.SET_NULL, ) clusters = models.ManyToManyField(Cluster) - is_hrp_beneficiary = models.BooleanField(default=False,null=True) + is_hrp_beneficiary = models.BooleanField(default=False, null=True) start_date = models.DateTimeField(blank=True, null=True) end_date = models.DateTimeField(blank=True, null=True) description = models.CharField(max_length=DESCRIPTION_MAX_LENGTH, blank=True, null=True) @@ -88,12 +88,18 @@ class Meta: class Organization(models.Model): """Organizations Model""" + TYPE_CHOICES = [ + ("National NGO", "National NGO"), + ("International NGO", "International NGO"), + ("Government", "Government"), + ("Business", "Business"), + ] countries = models.ManyToManyField(Location, blank=True) clusters = models.ManyToManyField(Cluster, blank=True) name = models.CharField(max_length=NAME_MAX_LENGTH, blank=True, null=True) code = models.CharField(max_length=NAME_MAX_LENGTH, blank=True, null=True) - type = models.CharField(max_length=NAME_MAX_LENGTH, blank=True, null=True) + type = models.CharField(max_length=NAME_MAX_LENGTH, choices=TYPE_CHOICES, blank=True, null=True) created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True) updated_at = models.DateTimeField(auto_now_add=True, blank=True, null=True) old_id = models.CharField("Old ID", max_length=NAME_MAX_LENGTH, blank=True, null=True) diff --git a/src/rh/resources.py b/src/rh/resources.py index b7c74f1e..e95b1ea8 100644 --- a/src/rh/resources.py +++ b/src/rh/resources.py @@ -2,7 +2,6 @@ from django.contrib.auth.models import User from import_export.widgets import ManyToManyWidget, ForeignKeyWidget from .models import ( - Cluster, Currency, Donor, @@ -99,12 +98,11 @@ class Meta: column_name="clusters", attribute="clusters", widget=ManyToManyWidget(Cluster, field="title", separator=",") ) - # activity planning start def dehydrate_activity_domain(self, project): activity_domain = list(project.activityplan_set.all()) return ",".join([child.activity_domain.name for child in activity_domain]) - + def dehydrate_activity_type(self, project): activity_types = list(project.activityplan_set.all()) return ",".join([child.activity_type.name for child in activity_types]) @@ -112,7 +110,7 @@ def dehydrate_activity_type(self, project): def dehydrate_indicators(self, project): activity_plan = project.activityplan_set.all() return ",".join([indicator.name for plan in activity_plan for indicator in plan.indicators.all()]) - + def dehydrate_beneficiary(self, project): activity_plan = list(project.activityplan_set.all()) return ",".join([child.beneficiary for child in activity_plan if child.beneficiary]) diff --git a/src/rh/templates/rh/projects/forms/organization_register_form.html b/src/rh/templates/rh/projects/forms/organization_register_form.html new file mode 100644 index 00000000..da73639a --- /dev/null +++ b/src/rh/templates/rh/projects/forms/organization_register_form.html @@ -0,0 +1,42 @@ +{% extends "../../../_base.html" %} +{% load static %} +{% load vite %} +{% block content %} +
+
+ image description +
+
+
+

Add Organization

+ +
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut non magna vel quam aliquet luctus. + Duis imperdiet vel urna eu hendrerit. Suspendisse tristique + odio ac quam porta, at pulvinar enim varius. +

+
+
+
+ +
+
+{% endblock content %} diff --git a/src/rh/urls.py b/src/rh/urls.py index 309d92a0..000dafa2 100644 --- a/src/rh/urls.py +++ b/src/rh/urls.py @@ -6,6 +6,8 @@ urlpatterns = [ path("", user_views.index, name="index"), path("home", user_views.home, name="home"), + # Organization Registration route + path("organization_register/", user_views.organization_register, name="organization_register"), # Projects CRUD path("projects/draft/", user_views.draft_projects_view, name="draft_projects"), path("projects/active/", user_views.active_projects_view, name="active_projects"), diff --git a/src/rh/views.py b/src/rh/views.py index bd7ba03c..3eaf7b82 100644 --- a/src/rh/views.py +++ b/src/rh/views.py @@ -15,7 +15,14 @@ from rh.resources import ProjectResource from .filters import ProjectsFilter -from .forms import ActivityPlanFormSet, BudgetProgressForm, DisaggregationFormSet, ProjectForm, TargetLocationFormSet +from .forms import ( + ActivityPlanFormSet, + BudgetProgressForm, + DisaggregationFormSet, + OrganizationRegisterForm, + ProjectForm, + TargetLocationFormSet, +) from .models import ( ActivityDomain, ActivityPlan, @@ -936,9 +943,7 @@ def copy_project_target_location(plan, location): # Duplicate the original target location # by retrieving it with the provided primary key. new_location = get_object_or_404(TargetLocation, pk=location.pk) - new_location.pk = ( - None # Generate a new primary key for the duplicated location. - ) + new_location.pk = None # Generate a new primary key for the duplicated location. new_location.save() # Save the duplicated location to the database. # Associate the duplicated location with the new activity plan. @@ -967,9 +972,7 @@ def copy_target_location_disaggregation_locations(location, disaggregation_locat try: # Duplicate the original disaggregation location by retrieving it with the provided primary key. new_disaggregation_location = get_object_or_404(DisaggregationLocation, pk=disaggregation_location.pk) - new_disaggregation_location.pk = ( - None # Generate a new primary key for the duplicated location. - ) + new_disaggregation_location.pk = None # Generate a new primary key for the duplicated location. new_disaggregation_location.save() # Save the duplicated location to the database. # Associate the duplicated disaggregation location with the new target location. @@ -1153,6 +1156,25 @@ def delete_budget_progress(request, pk): return JsonResponse({"success": True}) +# Registration Organizations +@login_required +def organization_register(request): + if request.method == "POST": + org_form = OrganizationRegisterForm(request.POST) + if org_form.is_valid(): + name = org_form.cleaned_data.get("name") + code = org_form.cleaned_data.get("code") + organization = org_form.save() + if organization: + messages.success(request, f"[{code}] {name} is registered successfully !") + else: + messages.error(request, "Something went wrong ! please try again ") + else: + org_form = OrganizationRegisterForm() + context = {"org_form": org_form} + return render(request, "rh/projects/forms/organization_register_form.html", context) + + def ProjectListView(request, flag): # project_list =json.loads(request.POST.get("projectList")) project = Project.objects.filter(user=request.user.id) diff --git a/src/static/rh/js/all_projects.js b/src/static/rh/js/all_projects.js index f84f12a5..f8486961 100644 --- a/src/static/rh/js/all_projects.js +++ b/src/static/rh/js/all_projects.js @@ -8,6 +8,7 @@ $(function () { const url = $(this).data('url'); window.location.href = url; }); + $('.js_multiselect').select2() // toggle between accordion arrow $("#activityAcc").on("click", function(){ let up =$(".activity-arrow-up"); diff --git a/src/static/rh/js/project_planning.js b/src/static/rh/js/project_planning.js index 9349e10f..3995ccf1 100644 --- a/src/static/rh/js/project_planning.js +++ b/src/static/rh/js/project_planning.js @@ -119,5 +119,4 @@ $(function () { }) $('.js_multiselect').select2() - }); diff --git a/src/stock/urls.py b/src/stock/urls.py index 54c0924a..94e08a76 100644 --- a/src/stock/urls.py +++ b/src/stock/urls.py @@ -12,7 +12,5 @@ user_views.submit_stock_report_form, name="submit_stock_report", ), - path("stocks/edit_submitted_stock/", - user_views.edit_submitted_stock, - name="edit_submitted_stock"), + path("stocks/edit_submitted_stock/", user_views.edit_submitted_stock, name="edit_submitted_stock"), ] diff --git a/src/stock/views.py b/src/stock/views.py index defa3d60..180bcc3b 100644 --- a/src/stock/views.py +++ b/src/stock/views.py @@ -168,5 +168,6 @@ def submit_stock_report_form(request, pk): StockReports.objects.filter(id=pk).update(submitted=True, submitted_at=datetime.datetime.now()) return redirect("all_stock_report") + def edit_submitted_stock(request): - return render(request, 'stock/edit_submitted_stock.html') \ No newline at end of file + return render(request, "stock/edit_submitted_stock.html") diff --git a/src/templates/_base.html b/src/templates/_base.html index 693725e5..53350b2a 100644 --- a/src/templates/_base.html +++ b/src/templates/_base.html @@ -101,6 +101,9 @@
  • My Cluster/Org
  • +
  • + Add Organization +
  • Logout
  • diff --git a/src/users/views.py b/src/users/views.py index 00d54970..94c6f342 100644 --- a/src/users/views.py +++ b/src/users/views.py @@ -143,7 +143,7 @@ def login_view(request): if request.method == "POST": email = request.POST["email"] password = request.POST.get("password") - user = authenticate(request,username=email, email=email, password=password) + user = authenticate(request, username=email, email=email, password=password) if user is not None: login(request, user)