From 8b5a4c83f3d37d420a3c8f8ba6880d831f2051e6 Mon Sep 17 00:00:00 2001 From: Angel Ramboi Date: Thu, 7 May 2020 20:09:10 +0200 Subject: [PATCH 01/11] Update seed script in preparation for the city model migration --- ro_help/hub/management/commands/seed.py | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ro_help/hub/management/commands/seed.py b/ro_help/hub/management/commands/seed.py index de42e6fc..a7d3bd76 100644 --- a/ro_help/hub/management/commands/seed.py +++ b/ro_help/hub/management/commands/seed.py @@ -3,7 +3,6 @@ import requests from faker import Faker -from django.conf import settings from django.contrib.auth.models import User, Group, Permission from django.core.management.base import BaseCommand from django.utils import timezone @@ -35,7 +34,7 @@ def random_avatar(): try: image = requests.get("https://source.unsplash.com/random", allow_redirects=False) return image.headers["Location"] - except: + except Exception: return "https://source.unsplash.com/random" @@ -45,31 +44,31 @@ def random_avatar(): "name": "Habitat for Humanity", "email": "habitat@habitat.ro", "description": """ - O locuință decentă poate rupe cercul sărăciei. Credem cu tărie în acest lucru din 1976, de când lucrăm pentru + O locuință decentă poate rupe cercul sărăciei. Credem cu tărie în acest lucru din 1976, de când lucrăm pentru viziunea noastră: o lume în care toți oamenii au posibilitatea să locuiască decent. Cu sprijinul nostru, peste 6 milioane de oameni din peste 70 de țări au un loc mai bun în care să trăiască, o casă nouă sau una - complet renovată.Suntem o asociație creștină, non-profit, ce lucrăm alături de oameni de pretutindeni, + complet renovată.Suntem o asociație creștină, non-profit, ce lucrăm alături de oameni de pretutindeni, din toate păturile sociale, rasele, religiile și naționalitățile pentru a elimina locuirea precară. """, "phone": "+40722644394", "address": "Str. Naum Râmniceanu, nr. 45 A, et.1, ap. 3, sector 1, Bucureşti 011616", "city": "Bucureşti", - "county": "Sector 1", + "county": "BUCURESTI", "avatar": "http://www.habitat.ro/wp-content/uploads/2014/11/logo.png", }, { "name": "Crucea Roșie", "email": "matei@crucearosie.ro", "description": """ - Crucea Rosie Romana asista persoanele vulnerabile in situatii de dezastre si de criza. Prin programele si + Crucea Rosie Romana asista persoanele vulnerabile in situatii de dezastre si de criza. Prin programele si activitatile sale in beneficiul societatii, contribuie la prevenirea si alinarea suferintei sub toate formele, - protejeaza sanatatea si viata, promoveaza respectul fata de demnitatea umana, fara nicio discriminare bazata + protejeaza sanatatea si viata, promoveaza respectul fata de demnitatea umana, fara nicio discriminare bazata pe nationalitate, rasa, sex, religie, varsta, apartenenta sociala sau politica. """, "phone": "+40213176006", "address": "Strada Biserica Amzei, nr. 29, Sector 1, Bucuresti", "city": "Bucuresti", - "county": "Sector 1", + "county": "BUCURESTI", "avatar": "https://crucearosie.ro/themes/redcross/images/emblema_crr_desktop.png", "accepts_transfer": True, "donations_description": "Monedă RON: RO44BRDE410SV20462054100 (Banca: BRD - Piata Romana)", @@ -78,19 +77,19 @@ def random_avatar(): "name": "MKBT: Make Better", "email": "contact@mkbt.ro", "description": """ - MKBT: Make Better has been working for urban development and regeneration in Romania since April 2014. - That is to say that we are drafting, validating and coordinating processes for local development and urban + MKBT: Make Better has been working for urban development and regeneration in Romania since April 2014. + That is to say that we are drafting, validating and coordinating processes for local development and urban regeneration in order to help as many cities become their best possible version and the best home for their inhabitants. As a local development advisor, we assist both public and private entities. - We substantiate our work on a thorough understanding of local needs and specificities of the communities we - work with. We acknowledge, at the same time, the global inter-connectivity of local challenges. Our proposed + We substantiate our work on a thorough understanding of local needs and specificities of the communities we + work with. We acknowledge, at the same time, the global inter-connectivity of local challenges. Our proposed solutions are therefore grounded in international best practice, while at the same time capitalizing – in a sustainable and harmonious manner – on local know how and resources. """, "phone": "+40213176006", "address": "Str. Popa Petre, Nr. 23, Sector 2, 020802, Bucharest, Romania.", "city": "Bucuresti", - "county": "Sector 2", + "county": "BUCURESTI", "avatar": "http://mkbt.ro/wp-content/uploads/2015/08/MKBT-logo-alb.png", }, ] @@ -101,11 +100,11 @@ def random_avatar(): "description": fake.text(), "phone": fake.phone_number(), "address": fake.address(), - "city": random.choice(["Arad", "Timisoara", "Oradea", "Cluj", "Bucuresti"]), - "county": random.choice(["ARAD", "TIMIS", "BIHOR", "CLUJ", "SECTOR 1", "SECTOR 2"]), + "city": ["Arad", "Timisoara", "Oradea", "Cluj-Napoca", "Bucuresti"][i], + "county": ["ARAD", "TIMIS", "BIHOR", "CLUJ", "BUCURESTI"][i], "avatar": random_avatar(), } - for _ in range(2) + for i in range(5) ] ) @@ -214,6 +213,7 @@ def handle(self, *args, **kwargs): ngo.save() for _ in range(20): + c_index = random.randint(0, 4) need = NGONeed.objects.create( **{ "ngo": ngo, @@ -222,8 +222,8 @@ def handle(self, *args, **kwargs): "description": fake.text(), "title": fake.text(), "resolved_on": random.choice([None, timezone.now()]), - "city": random.choice(["Arad", "Timisoara", "Oradea", "Cluj", "Bucuresti"]), - "county": random.choice(["ARAD", "TIMIS", "BIHOR", "CLUJ", "SECTOR 1", "SECTOR 2"]), + "city": ["Arad", "Timisoara", "Oradea", "Cluj-Napoca", "Bucuresti"][c_index], + "county": ["ARAD", "TIMIS", "BIHOR", "CLUJ", "BUCURESTI"][c_index], } ) From fc8c8904f2b4dc43bfdc17e3934483bc4f9f9c5b Mon Sep 17 00:00:00 2001 From: Angel Ramboi Date: Fri, 8 May 2020 12:36:23 +0200 Subject: [PATCH 02/11] Migrate models with city field to the city model --- requirements-dev.txt | 1 + requirements.txt | 2 +- ro_help/hub/admin.py | 21 +- ro_help/hub/context_processors.py | 8 + ro_help/hub/forms.py | 12 +- ro_help/hub/management/commands/seed.py | 32 ++- .../migrations/0049_models_to_city_model.py | 121 +++++++++ ro_help/hub/models.py | 47 +++- ro_help/hub/templates/base.html | 15 +- .../hub/templates/ngo/register_request.html | 5 +- ro_help/hub/urls.py | 2 + ro_help/hub/views.py | 16 +- ro_help/locale/en/LC_MESSAGES/django.mo | Bin 421 -> 421 bytes ro_help/locale/en/LC_MESSAGES/django.po | 233 ++++++++--------- ro_help/locale/hu/LC_MESSAGES/django.mo | Bin 421 -> 421 bytes ro_help/locale/hu/LC_MESSAGES/django.po | 233 ++++++++--------- ro_help/locale/ro/LC_MESSAGES/django.mo | Bin 78841 -> 78841 bytes ro_help/locale/ro/LC_MESSAGES/django.po | 235 +++++++++--------- ro_help/ro_help/settings/base.py | 5 +- ro_help/ro_help/settings/dev.py | 4 +- 20 files changed, 604 insertions(+), 388 deletions(-) create mode 100644 ro_help/hub/context_processors.py create mode 100644 ro_help/hub/migrations/0049_models_to_city_model.py diff --git a/requirements-dev.txt b/requirements-dev.txt index 1db77cd8..174ccc16 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,3 +6,4 @@ django-debug-toolbar==2.2 Faker==4.0.2 pytest==5.4.1 +ipython==7.14.0 diff --git a/requirements.txt b/requirements.txt index d5616b9e..cdbef001 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ django-file-resubmit>=0.5.2,<1.0.0 django-extensions==2.2.8 requests==2.23.0 django-admin-rangefilter>=0.5.4,<1.0.0 -admin-totals>=1.0.1,<2.0.0 \ No newline at end of file +admin-totals>=1.0.1,<2.0.0 diff --git a/ro_help/hub/admin.py b/ro_help/hub/admin.py index f163bb1b..36082a66 100644 --- a/ro_help/hub/admin.py +++ b/ro_help/hub/admin.py @@ -85,11 +85,9 @@ class NGOAdmin(admin.ModelAdmin): form = NGOForm list_display = ("name", "contact_name", "county", "city", "accepts_transfer", "accepts_mobilpay", "created") - list_filter = ( - "city", - "county", - ) + list_filter = ("county",) search_fields = ("name", "email") + autocomplete_fields = ["city"] inlines = [NGOAccountInline] def get_queryset(self, request): @@ -102,14 +100,17 @@ def get_queryset(self, request): return qs def get_readonly_fields(self, request, obj=None): + readonly_fields = [] if not obj: - return [] + return readonly_fields user = request.user if not user.groups.filter(name=ADMIN_GROUP_NAME).exists(): - return ["users"] + readonly_fields.append("users") + + readonly_fields.extend(["city_old", "county"]) - return [] + return readonly_fields def has_accounts(self, request, no_accounts): for account_idx in range(no_accounts): @@ -202,7 +203,7 @@ class NGONeedAdmin(admin.ModelAdmin): list_display = ("title", "ngo", "urgency", "kind", "created", "responses", "resolved_on", "closed_on") list_filter = (NGOFilter, ActiveNGONeedFilter, "urgency", "kind", "ngo__city", "ngo__county") - readonly_fields = ["resolved_on", "closed_on"] + readonly_fields = ["resolved_on", "closed_on", "city_old", "county"] inlines = [NGOHelperInline] actions = ["resolve_need", "close_need"] search_fields = ( @@ -212,6 +213,7 @@ class NGONeedAdmin(admin.ModelAdmin): "ngo__name", "ngo__email", ) + autocomplete_fields = ["city"] class Media: pass @@ -331,10 +333,11 @@ class RegisterNGORequestAdmin(admin.ModelAdmin): "get_statute", ] actions = ["create_account", "close_request"] - readonly_fields = ["active", "resolved_on", "registered_on", "get_avatar"] + readonly_fields = ["active", "resolved_on", "registered_on", "get_avatar", "city_old", "county"] list_filter = ("city", "county", "registered_on", "closed") inlines = [RegisterNGORequestVoteInline] search_fields = ("name",) + autocomplete_fields = ["city"] def votes_count(self, obj): return obj.votes_count diff --git a/ro_help/hub/context_processors.py b/ro_help/hub/context_processors.py new file mode 100644 index 00000000..51d431fd --- /dev/null +++ b/ro_help/hub/context_processors.py @@ -0,0 +1,8 @@ +from django.conf import settings + + +def hub_settings(context): + return { + "DEBUG": settings.DEBUG, + "ANALYTICS_ENABLED": settings.ANALYTICS_ENABLED, + } diff --git a/ro_help/hub/forms.py b/ro_help/hub/forms.py index ad4d94ca..000a57b4 100644 --- a/ro_help/hub/forms.py +++ b/ro_help/hub/forms.py @@ -1,12 +1,14 @@ from django import forms +from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError from django_crispy_bulma.widgets import EmailInput -from hub import models from captcha.fields import ReCaptchaField from captcha.widgets import ReCaptchaV3 +from hub import models + class NGOHelperForm(forms.ModelForm): captcha = ReCaptchaField(widget=ReCaptchaV3(attrs={"required_score": 0.3, "action": "help"}), label="") @@ -56,7 +58,7 @@ class Meta: model = models.RegisterNGORequest fields = [ "name", - "county", + # "county", "city", "address", "email", @@ -73,6 +75,12 @@ class Meta: ] widgets = { "email": EmailInput(), + "city": forms.Select( + attrs={ + "class": "selectAutoComplete", + "data-url": reverse_lazy("city-autocomplete"), + } + ), # # "has_netopia_contract": forms.CheckboxInput(), # "avatar": AdminResubmitImageWidget, # "last_balance_sheet": AdminResubmitFileWidget, diff --git a/ro_help/hub/management/commands/seed.py b/ro_help/hub/management/commands/seed.py index a7d3bd76..ded20e3a 100644 --- a/ro_help/hub/management/commands/seed.py +++ b/ro_help/hub/management/commands/seed.py @@ -23,6 +23,7 @@ DSU_GROUP_NAME, FFC_GROUP_NAME, NGOReportItem, + City, ) from mobilpay.models import PaymentOrder @@ -51,9 +52,9 @@ def random_avatar(): din toate păturile sociale, rasele, religiile și naționalitățile pentru a elimina locuirea precară. """, "phone": "+40722644394", - "address": "Str. Naum Râmniceanu, nr. 45 A, et.1, ap. 3, sector 1, Bucureşti 011616", - "city": "Bucureşti", - "county": "BUCURESTI", + "address": "Str. Naum Râmniceanu, nr. 45 A, et.1, ap. 3, sector 1, Bucuresti 011616", + "city": "Bucuresti", + "county": "Bucuresti", "avatar": "http://www.habitat.ro/wp-content/uploads/2014/11/logo.png", }, { @@ -68,7 +69,7 @@ def random_avatar(): "phone": "+40213176006", "address": "Strada Biserica Amzei, nr. 29, Sector 1, Bucuresti", "city": "Bucuresti", - "county": "BUCURESTI", + "county": "Bucuresti", "avatar": "https://crucearosie.ro/themes/redcross/images/emblema_crr_desktop.png", "accepts_transfer": True, "donations_description": "Monedă RON: RO44BRDE410SV20462054100 (Banca: BRD - Piata Romana)", @@ -89,7 +90,7 @@ def random_avatar(): "phone": "+40213176006", "address": "Str. Popa Petre, Nr. 23, Sector 2, 020802, Bucharest, Romania.", "city": "Bucuresti", - "county": "BUCURESTI", + "county": "Bucuresti", "avatar": "http://mkbt.ro/wp-content/uploads/2015/08/MKBT-logo-alb.png", }, ] @@ -101,7 +102,7 @@ def random_avatar(): "phone": fake.phone_number(), "address": fake.address(), "city": ["Arad", "Timisoara", "Oradea", "Cluj-Napoca", "Bucuresti"][i], - "county": ["ARAD", "TIMIS", "BIHOR", "CLUJ", "BUCURESTI"][i], + "county": ["Arad", "Timis", "Bihor", "Cluj", "Bucuresti"][i], "avatar": random_avatar(), } for i in range(5) @@ -204,7 +205,20 @@ def handle(self, *args, **kwargs): NGONeed.objects.filter(kind="money").exclude(ngo__name__in=["Code4Romania", "Crucea Roșie"]).delete() + cities = [ + {"city": "Arad", "county": "Arad"}, + {"city": "Timisoara", "county": "Timis"}, + {"city": "Oradea", "county": "Bihor"}, + {"city": "Cluj-Napoca", "county": "Cluj"}, + {"city": "Bucuresti", "county": "Bucuresti"}, + ] + for city_data in cities: + City.objects.get_or_create(**city_data) + for details in NGOS: + details['city'] = City.objects.get(city=details['city'], county=details['county']) + details['county'] = details['county'].upper() + ngo, _ = NGO.objects.get_or_create(**details) owner = random.choice([ngo_user, admin_user, None]) @@ -213,7 +227,7 @@ def handle(self, *args, **kwargs): ngo.save() for _ in range(20): - c_index = random.randint(0, 4) + random_city = City.objects.order_by("?").first() need = NGONeed.objects.create( **{ "ngo": ngo, @@ -222,8 +236,8 @@ def handle(self, *args, **kwargs): "description": fake.text(), "title": fake.text(), "resolved_on": random.choice([None, timezone.now()]), - "city": ["Arad", "Timisoara", "Oradea", "Cluj-Napoca", "Bucuresti"][c_index], - "county": ["ARAD", "TIMIS", "BIHOR", "CLUJ", "BUCURESTI"][c_index], + "city": random_city, + "county": random_city.county.upper(), } ) diff --git a/ro_help/hub/migrations/0049_models_to_city_model.py b/ro_help/hub/migrations/0049_models_to_city_model.py new file mode 100644 index 00000000..1c9c272a --- /dev/null +++ b/ro_help/hub/migrations/0049_models_to_city_model.py @@ -0,0 +1,121 @@ +# Custom migration +from django.db import migrations, models +import django.db.models.deletion + + +def map_city_field_to_city_model(apps, schema_editor): + print(f"\n{'*'*80}\n") + print("[INFO]: START of data migration") + + City = apps.get_model("hub", "City") + + NGO = apps.get_model("hub", "NGO") + NGONeed = apps.get_model("hub", "NGONeed") + PersonalRequest = apps.get_model("hub", "PersonalRequest") + RegisterNGORequest = apps.get_model("hub", "RegisterNGORequest") + + models = [NGO, NGONeed, PersonalRequest, RegisterNGORequest] + + for model in models: + for n in model.objects.all(): + try: + if "sector" in n.county.lower(): + n.county = "BUCURESTI" + + city = n.city.lower().strip() + # remove romanian characters + for i in [("ț", "t"), ("ș", "s"), ("î", "i"), ("â", "a"), ("ă", "a")]: + city = city.replace(i[0], i[1]) + + # hack to format cities like "alba-iulia" properly + city = " ".join(city.split()).replace("-", " ").title().replace(" ", "-") + + # another fail-safe in case the "sector" one did not catch this + if city == "Bucuresti": + n.county = "BUCURESTI" + + city = City.objects.get(city__iexact=city, county__iexact=n.county) + n.city_fk = city + n.save() + print(f"[INFO]: Migrated: {n}") + except City.DoesNotExist: + print( + f"[ERROR]: Could not migrate properly: {n}. " + f"City object with fields {n.city} ({n.county}) not found." + ) + + print("[INFO]: END of data migration\n") + print(f"\n{'*'*80}\n") + + +class Migration(migrations.Migration): + + dependencies = [ + ("hub", "0048_auto_20200429_0736"), + ] + + operations = [ + migrations.AddField( + model_name="ngo", + name="city_fk", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to="hub.City"), + preserve_default=False, + ), + migrations.AddField( + model_name="ngoneed", + name="city_fk", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to="hub.City"), + preserve_default=False, + ), + migrations.AddField( + model_name="personalrequest", + name="city_fk", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to="hub.City"), + preserve_default=False, + ), + migrations.AddField( + model_name="registerngorequest", + name="city_fk", + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to="hub.City"), + preserve_default=False, + ), + # data migration + migrations.RunPython(map_city_field_to_city_model), + migrations.RenameField(model_name="ngo", old_name="city", new_name="city_old",), + migrations.RenameField(model_name="ngo", old_name="city_fk", new_name="city",), + migrations.RenameField(model_name="ngoneed", old_name="city", new_name="city_old",), + migrations.RenameField(model_name="ngoneed", old_name="city_fk", new_name="city",), + migrations.RenameField(model_name="personalrequest", old_name="city", new_name="city_old",), + migrations.RenameField(model_name="personalrequest", old_name="city_fk", new_name="city",), + migrations.RenameField(model_name="registerngorequest", old_name="city", new_name="city_old",), + migrations.RenameField(model_name="registerngorequest", old_name="city_fk", new_name="city",), + migrations.AlterModelOptions(name="city", options={"verbose_name": "City", "verbose_name_plural": "cities"},), + migrations.AlterField( + model_name="ngo", + name="city", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.PROTECT, to="hub.City", verbose_name="City" + ), + ), + migrations.AlterField( + model_name="ngoneed", + name="city", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.PROTECT, to="hub.City", verbose_name="City" + ), + ), + migrations.AlterField( + model_name="personalrequest", + name="city", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.PROTECT, to="hub.City", verbose_name="City" + ), + ), + migrations.AlterField( + model_name="registerngorequest", + name="city", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.PROTECT, to="hub.City", verbose_name="City" + ), + ), + ] diff --git a/ro_help/hub/models.py b/ro_help/hub/models.py index a716acaf..526372d4 100644 --- a/ro_help/hub/models.py +++ b/ro_help/hub/models.py @@ -195,11 +195,12 @@ class City(models.Model): is_county_residence = models.BooleanField(_("Is county residence"), default=False) class Meta: + verbose_name = _("City") verbose_name_plural = _("cities") unique_together = ["city", "county"] def __str__(self): - return self.city + return f"{self.city} ({self.county})" def save(self, *args, **kwargs): self.is_county_residence = False @@ -237,7 +238,8 @@ class NGO(TimeStampedModel): cif = models.CharField("CIF", max_length=20, null=True, blank=True) cui = models.CharField("CUI", max_length=20, null=True, blank=True) county = models.CharField(_("County"), choices=COUNTY.to_choices(), max_length=50) - city = models.CharField(_("City"), max_length=100) + city = models.ForeignKey("City", on_delete=models.PROTECT, null=True, verbose_name=_("City")) + city_old = models.CharField(_("City"), max_length=100) avatar = models.ImageField(_("Logo"), max_length=300, storage=PublicMediaStorageClass()) last_balance_sheet = models.FileField( @@ -290,6 +292,14 @@ def get_avatar(self): else: return None + def save(self, *args, **kwargs): + if self.city: + self.county = self.city.county.upper() + else: + self.county = "" + + super().save(*args, **kwargs) + class Meta: verbose_name_plural = _("My organizations") verbose_name = _("My organization") @@ -346,7 +356,8 @@ class NGONeed(TimeStampedModel): kind = models.CharField(_("Kind"), choices=KIND.to_choices(), default=KIND.default(), max_length=10) urgency = models.CharField(_("Urgency"), choices=URGENCY.to_choices(), default=URGENCY.default(), max_length=10) - city = models.CharField(_("City"), max_length=100) + city = models.ForeignKey("City", on_delete=models.PROTECT, null=True, verbose_name=_("City")) + city_old = models.CharField(_("City"), max_length=100) county = models.CharField(_("County"), choices=COUNTY.to_choices(), max_length=50) resource_tags = models.ManyToManyField("ResourceTag", blank=True, related_name="needs") @@ -359,6 +370,14 @@ class NGONeed(TimeStampedModel): def __str__(self): return f"{self.ngo.name}: {self.urgency} {self.kind}" + def save(self, *args, **kwargs): + if self.city: + self.county = self.city.county.upper() + else: + self.county = "" + + super().save(*args, **kwargs) + class Meta: verbose_name_plural = _("Manage needs") verbose_name = _("Manage need") @@ -404,7 +423,8 @@ class PersonalRequest(TimeStampedModel): name = models.CharField(_("Name"), max_length=254,) email = models.EmailField(_("Email"), null=True, blank=True) phone = models.CharField(_("Phone"), max_length=15) - city = models.CharField(_("City"), max_length=100) + city = models.ForeignKey("City", on_delete=models.PROTECT, null=True, verbose_name=_("City")) + city_old = models.CharField(_("City"), max_length=100) county = models.CharField(_("County"), choices=COUNTY.to_choices(), max_length=50) address = models.CharField(_("Address"), max_length=400, null=True, blank=True) organization = models.CharField(_("Organization"), max_length=400, null=True, blank=True) @@ -416,6 +436,14 @@ class PersonalRequest(TimeStampedModel): def __str__(self): return self.name + def save(self, *args, **kwargs): + if self.city: + self.county = self.city.county.upper() + else: + self.county = "" + + super().save(*args, **kwargs) + class RegisterNGORequest(TimeStampedModel): name = models.CharField(_("Name"), max_length=254) @@ -437,7 +465,8 @@ class RegisterNGORequest(TimeStampedModel): contact_phone = models.CharField(_("Contact person's phone"), max_length=15) has_netopia_contract = models.BooleanField(_("Has contract with Netopia"), default=False) address = models.CharField(_("Address"), max_length=400) - city = models.CharField(_("City"), max_length=100) + city = models.ForeignKey("City", on_delete=models.PROTECT, null=True, verbose_name=_("City")) + city_old = models.CharField(_("City"), max_length=100) county = models.CharField(_("County"), choices=COUNTY.to_choices(), max_length=50) social_link = models.CharField(_("Link to website or Facebook"), max_length=512, null=True, blank=True) @@ -523,6 +552,14 @@ def activate(self, request, ngo_group=None): self.active = True self.save() + def save(self, *args, **kwargs): + if self.city: + self.county = self.city.county.upper() + else: + self.county = "" + + super().save(*args, **kwargs) + def __str__(self): return self.name diff --git a/ro_help/hub/templates/base.html b/ro_help/hub/templates/base.html index ab9939e7..ebb5318b 100644 --- a/ro_help/hub/templates/base.html +++ b/ro_help/hub/templates/base.html @@ -1,6 +1,4 @@ -{% load static %} -{% load meta_tags %} - +{% load static meta_tags %} @@ -8,18 +6,23 @@ {% block meta_tags %}{% with title="Luptă împotriva pandemiei de Covid-19." %}{% meta_tags %}{% endwith %}{% endblock %} + {% block title %}RoHelp{% endblock %} + + {% include "favicons.html" %} + - {% block title %}RoHelp{% endblock %} + + {% if ANALYTICS_ENABLED %} + +{% endblock %} + {% block extra-header %} {% endblock extra-header %} @@ -38,4 +52,34 @@ {% endif %} + + {% endblock %} diff --git a/ro_help/hub/views.py b/ro_help/hub/views.py index 6be3aea4..00c80b75 100644 --- a/ro_help/hub/views.py +++ b/ro_help/hub/views.py @@ -296,14 +296,17 @@ def get_success_message(self, cleaned_data): class CityAutocomplete(View): def get(self, request): - response = [] + response = {"results": []} - county = request.GET.get("county") + county = request.GET.get("c") q = request.GET.get("q") - if county and q and len(q) > 0: - rows = City.objects.filter(county__iexact=county, city__istartswith=q).values_list("id", "city", named=True) - response = [{"v": row.id, "t": row.city} for row in rows] + if county and q and len(q) > 1: + cities = City.objects.filter(county__iexact=county, city__istartswith=q,).values_list( + "id", "city", named=True + ) + + response["results"] = [{"id": item.id, "text": item.city} for item in cities] return JsonResponse(response, safe=False) From 1cf0bb32683d8781062c7162ce41c2ac84e7f0fc Mon Sep 17 00:00:00 2001 From: Angel Ramboi Date: Fri, 22 May 2020 21:17:12 +0200 Subject: [PATCH 11/11] Make city dropdown in NGO registration autofill on county change --- ro_help/hub/forms.py | 11 ++--- ro_help/hub/templates/base.html | 4 +- .../hub/templates/ngo/register_request.html | 44 ------------------- ro_help/hub/views.py | 17 +++---- ro_help/static/js/main.js | 43 ++++++++++++++++++ ro_help/static/js/navbar.js | 25 ----------- 6 files changed, 53 insertions(+), 91 deletions(-) create mode 100644 ro_help/static/js/main.js delete mode 100644 ro_help/static/js/navbar.js diff --git a/ro_help/hub/forms.py b/ro_help/hub/forms.py index f7ee21ee..852ae214 100644 --- a/ro_help/hub/forms.py +++ b/ro_help/hub/forms.py @@ -75,13 +75,7 @@ class Meta: ] widgets = { "email": EmailInput(), - "city": forms.Select( - attrs={ - "style": "width:200px;height:40px;", - "disabled": "true", - "data-ajax--url": reverse_lazy("city-autocomplete"), - } - ), + "city": forms.Select(attrs={"data-url": reverse_lazy("city-autocomplete"),}), # # "has_netopia_contract": forms.CheckboxInput(), # "avatar": AdminResubmitImageWidget, # "last_balance_sheet": AdminResubmitFileWidget, @@ -92,6 +86,9 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["city"].queryset = models.City.objects.none() + if "city" not in self.data: + self.fields["city"].widget.attrs.update({"disabled": "true"}) + if "county" in self.data: try: county = self.data.get("county") diff --git a/ro_help/hub/templates/base.html b/ro_help/hub/templates/base.html index c57d1d01..439789c3 100644 --- a/ro_help/hub/templates/base.html +++ b/ro_help/hub/templates/base.html @@ -16,11 +16,11 @@ {% include "favicons.html" %} - + {% if ANALYTICS_ENABLED %} @@ -54,8 +54,6 @@ {% endif %} - {% block extra_head %}{% endblock %} - diff --git a/ro_help/hub/templates/ngo/register_request.html b/ro_help/hub/templates/ngo/register_request.html index ac512872..66559732 100644 --- a/ro_help/hub/templates/ngo/register_request.html +++ b/ro_help/hub/templates/ngo/register_request.html @@ -6,20 +6,6 @@ {% block kind-kind-filters %} {% endblock kind-kind-filters %} -{% block extra_head %} - - - - -{% endblock %} - {% block extra-header %} {% endblock extra-header %} @@ -52,34 +38,4 @@ {% endif %} - - {% endblock %} diff --git a/ro_help/hub/views.py b/ro_help/hub/views.py index 00c80b75..d826a2cd 100644 --- a/ro_help/hub/views.py +++ b/ro_help/hub/views.py @@ -296,18 +296,11 @@ def get_success_message(self, cleaned_data): class CityAutocomplete(View): def get(self, request): - response = {"results": []} - - county = request.GET.get("c") - q = request.GET.get("q") - - if county and q and len(q) > 1: - cities = City.objects.filter(county__iexact=county, city__istartswith=q,).values_list( - "id", "city", named=True - ) - - response["results"] = [{"id": item.id, "text": item.city} for item in cities] - + response = [] + county = request.GET.get("county") + if county: + cities = City.objects.filter(county__iexact=county).values_list("id", "city", named=True) + response = [{"id": item.id, "city": item.city} for item in cities] return JsonResponse(response, safe=False) diff --git a/ro_help/static/js/main.js b/ro_help/static/js/main.js new file mode 100644 index 00000000..e46254ea --- /dev/null +++ b/ro_help/static/js/main.js @@ -0,0 +1,43 @@ +document.addEventListener("DOMContentLoaded", () => { + // Get all "navbar-burger" elements + const $navbarBurgers = Array.prototype.slice.call( + document.querySelectorAll(".navbar-burger"), + 0 + ); + + // Check if there are any navbar burgers + if ($navbarBurgers.length > 0) { + // Add a click event on each of them + $navbarBurgers.forEach((el) => { + el.addEventListener("click", () => { + // Get the target from the "data-target" attribute + const target = el.dataset.target; + const $target = document.getElementById(target); + + // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" + el.classList.toggle("is-active"); + $target.classList.toggle("is-active"); + }); + }); + } + + // registration city select lazy load + const countySelect = $("#id_county"); + const citySelect = $("#id_city"); + + countySelect.change(async function () { + const cityResponse = await fetch( + `${citySelect.data("url")}?county=${this.value}` + ); + const cityList = await cityResponse.json(); + + const cityHtml = []; + citySelect.empty(); + cityHtml.push(``); + $.each(cityList, function (_, value) { + cityHtml.push(``); + }); + citySelect.append(cityHtml); + citySelect.attr("disabled", false); + }); +}); diff --git a/ro_help/static/js/navbar.js b/ro_help/static/js/navbar.js deleted file mode 100644 index 9b18a3e8..00000000 --- a/ro_help/static/js/navbar.js +++ /dev/null @@ -1,25 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - - // Get all "navbar-burger" elements - const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); - - // Check if there are any navbar burgers - if ($navbarBurgers.length > 0) { - - // Add a click event on each of them - $navbarBurgers.forEach( el => { - el.addEventListener('click', () => { - - // Get the target from the "data-target" attribute - const target = el.dataset.target; - const $target = document.getElementById(target); - - // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu" - el.classList.toggle('is-active'); - $target.classList.toggle('is-active'); - - }); - }); - } - -}); \ No newline at end of file