diff --git a/deployment/.env b/deployment/.env index c78c3f8..31c0bd8 100644 --- a/deployment/.env +++ b/deployment/.env @@ -248,7 +248,7 @@ PYTHONPATH=/home/web/django_project:/geonode USE_DEFAULT_GEOSERVER_STYLE=False INITIAL_FIXTURES=True -VERSION=4.4.5 +VERSION=4.4.6 ISTSOS_VERSION=2.4.1-2 # ------ GEOSERVER ------ diff --git a/deployment/docker-compose.yml b/deployment/docker-compose.yml index efee098..fa7ba29 100644 --- a/deployment/docker-compose.yml +++ b/deployment/docker-compose.yml @@ -59,9 +59,6 @@ services: # Geoserver backend geoserver: - # TODO: - # Using older geoserver - # image: kartoza/sadc_gip:geoserver-3.1.netcfd image: kartoza/geoserver:2.23.0-geo-v2023.09.15 healthcheck: test: "curl --fail --silent --write-out 'HTTP CODE : %{http_code}\n' --output /dev/null http://127.0.0.1:8080/geoserver/rest/workspaces/geonode.html" @@ -71,11 +68,6 @@ services: start_period: 60s env_file: - .env - - # TODO: - # Uncomment this if using netcfd - # environment: - # - DATABASE_URL=postgres://geonode:geonode@db:5432/geonode volumes: - geodatadir:/spcgeonode-geodatadir/ ports: @@ -105,7 +97,7 @@ services: - POSTGRES_USER=${GEONODE_DATABASE_USER} - POSTGRES_PASSWORD=${GEONODE_DATABASE_PASSWORD} volumes: - - database:/var/lib/postgresql/16/ + - ./volumes/database:/var/lib/postgresql/16/ - ./backups:/backups restart: on-failure ports: diff --git a/django_project/gwml2 b/django_project/gwml2 index e802410..40746ec 160000 --- a/django_project/gwml2 +++ b/django_project/gwml2 @@ -1 +1 @@ -Subproject commit e80241077d4d149e857055bc105ba937741fa96a +Subproject commit 40746ecc11cc2d9d033ec59bb21102e2bd619ddd diff --git a/django_project/igrac/admin.py b/django_project/igrac/admin.py index 5a6740c..a8b4b81 100644 --- a/django_project/igrac/admin.py +++ b/django_project/igrac/admin.py @@ -1,6 +1,7 @@ from adminsortable.admin import SortableAdmin from django.contrib import admin from django.http import HttpResponseRedirect +from django.urls import reverse from django.utils.html import format_html from django.utils.translation import ugettext_lazy as _ from preferences.admin import PreferencesAdmin @@ -15,6 +16,7 @@ from igrac.models.groundwater_layer import GroundwaterLayer from igrac.models.map_slug import MapSlugMapping from igrac.models.profile import IgracProfile +from igrac.models.registration_page import RegistrationPage from igrac.models.site_preference import SitePreference @@ -118,3 +120,25 @@ def _organisations(self, obj: GroundwaterLayer): admin.site.register(GroundwaterLayer, GroundwaterLayerAdmin) + + +class RegistrationPageAdmin(admin.ModelAdmin): + list_display = ('code', 'user', 'created_at', 'url') + readonly_fields = ('code', 'user', 'created_at', 'url') + + def url(self, obj: RegistrationPage): + """Return registration url.""" + if obj.user: + return 'Link is not valid' + if obj.code: + url = reverse( + 'account_signup_with_code', + kwargs={'code': obj.code} + ) + return format_html( + f'Registration URL' + ) + return '-' + + +admin.site.register(RegistrationPage, RegistrationPageAdmin) diff --git a/django_project/igrac/forms/groundwater_layer.py b/django_project/igrac/forms/groundwater_layer.py index b93ec33..dc754dd 100644 --- a/django_project/igrac/forms/groundwater_layer.py +++ b/django_project/igrac/forms/groundwater_layer.py @@ -1,3 +1,4 @@ +import time import xml.etree.ElementTree as ET import requests @@ -81,14 +82,14 @@ def update_sql(self, tree): elif well_type == GGMN: mv = 'mv_well_ggmn' sql = ( - "select id, ggis_uid, original_id, name, feature_type,purpose, status, organisation, " - 'number_of_measurements_level as "groundwater_level_data", ' - 'number_of_measurements_quality as "groundwater_quality_data", ' - 'number_of_measurements_yield as "abstraction_discharge", ' - "country, year_of_drilling, aquifer_name, aquifer_type,manager, detail, location, created_at, created_by, last_edited_at, last_edited_by " - f"from {mv} where organisation_id IN (" + - f"{','.join(organisations)}" + - ") order by created_at DESC" + "select id, ggis_uid, original_id, name, feature_type,purpose, status, organisation, " + 'number_of_measurements_level as "groundwater_level_data", ' + 'number_of_measurements_quality as "groundwater_quality_data", ' + 'number_of_measurements_yield as "abstraction_discharge", ' + "country, year_of_drilling, aquifer_name, aquifer_type,manager, detail, location, created_at, created_by, last_edited_at, last_edited_by " + f"from {mv} where organisation_id IN (" + + f"{','.join(organisations)}" + + ") order by created_at DESC" ) tree.find('metadata/entry/virtualTable/sql').text = sql @@ -101,6 +102,7 @@ class CreateGroundwaterLayerForm(_BaseGroundwaterLayerForm): help_text='The layer name that will be created.', widget=forms.TextInput(attrs={'style': 'width:500px'}) ) + loop = 1 def clean_well_type(self): """Well type.""" @@ -143,6 +145,29 @@ def clean_name(self): ) return name + def get_dataset( + self, target_layer_name, workspace, store + ): + """Get dataset.""" + if self.loop < 10: + call_command('updatelayers', filter=target_layer_name) + try: + dataset = Dataset.objects.get( + workspace=workspace, store=store, name=target_layer_name + ) + if dataset and self.target_layer: + dataset.use_featureinfo_custom_template = self.target_layer.use_featureinfo_custom_template + dataset.featureinfo_custom_template = self.target_layer.featureinfo_custom_template + dataset.save() + + return dataset + except Dataset.DoesNotExist: + time.sleep(2) + self.loop += 1 + return self.get_dataset(target_layer_name, workspace, store) + else: + return None + def run(self): """Run it for duplication data.""" name = self.cleaned_data['name'] @@ -197,19 +222,7 @@ def run(self): if style: layer.default_style = style gs_catalog.save(layer) - call_command('updatelayers', filter=target_layer_name) - try: - dataset = Dataset.objects.get( - workspace=workspace, store=store, name=target_layer_name - ) - if dataset and self.target_layer: - dataset.use_featureinfo_custom_template = self.target_layer.use_featureinfo_custom_template - dataset.featureinfo_custom_template = self.target_layer.featureinfo_custom_template - dataset.save() - - return dataset - except Dataset.DoesNotExist: - return None + return self.get_dataset(target_layer_name, workspace, store) else: raise Exception(r.content) diff --git a/django_project/igrac/migrations/0011_registrationpage.py b/django_project/igrac/migrations/0011_registrationpage.py new file mode 100644 index 0000000..9906e7c --- /dev/null +++ b/django_project/igrac/migrations/0011_registrationpage.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.20 on 2024-02-23 06:10 + +import datetime +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('igrac', '0010_alter_groundwaterlayer_options'), + ] + + operations = [ + migrations.CreateModel( + name='RegistrationPage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.TextField(max_length=16, unique=True)), + ('created_at', models.DateTimeField(blank=True, default=datetime.datetime.now)), + ('note', models.TextField(blank=True, null=True)), + ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/django_project/igrac/models/groundwater_layer.py b/django_project/igrac/models/groundwater_layer.py index b57b197..6a46782 100644 --- a/django_project/igrac/models/groundwater_layer.py +++ b/django_project/igrac/models/groundwater_layer.py @@ -1,5 +1,7 @@ from django.contrib.gis.db import models from django.contrib.postgres.fields import ArrayField +from django.db.models.signals import post_delete +from django.dispatch import receiver from geonode.layers.models import Dataset from igrac.models.site_preference import SitePreference @@ -29,3 +31,11 @@ def assign_template(self, target_layer=None): layer.use_featureinfo_custom_template = target_layer.use_featureinfo_custom_template layer.featureinfo_custom_template = target_layer.featureinfo_custom_template layer.save() + + +@receiver(post_delete, sender=GroundwaterLayer) +def groundwater_layer_deleted( + sender, instance: GroundwaterLayer, using, **kwargs +): + if instance.layer: + instance.layer.delete() diff --git a/django_project/igrac/models/registration_page.py b/django_project/igrac/models/registration_page.py new file mode 100644 index 0000000..a315a92 --- /dev/null +++ b/django_project/igrac/models/registration_page.py @@ -0,0 +1,48 @@ +"""Registration page that needs to be used for register.""" + +import random +import string +from datetime import datetime + +from django.contrib.auth import get_user_model +from django.db import models + +User = get_user_model() + + +def id_generator( + size=36, + chars=string.ascii_letters + string.digits + '+_-' +): + """ID Generator.""" + return ''.join(random.choice(chars) for _ in range(size)) + + +class RegistrationPage(models.Model): + """Registration page that has random uuid. + + User can just register through this model. + When user is created through this page, it will be invalid + and need to create new one. + """ + + user = models.OneToOneField( + User, on_delete=models.SET_NULL, + blank=True, null=True + ) + code = models.TextField(max_length=16, unique=True) + created_at = models.DateTimeField( + default=datetime.now, blank=True + ) + note = models.TextField( + blank=True, null=True + ) + + def __str__(self): + return str(self.code) + + def save(self, *args, **kwargs): + """Save model.""" + if not self.code: + self.code = id_generator() + return super().save(*args, **kwargs) diff --git a/django_project/igrac/templates/account/signup-not-found.html b/django_project/igrac/templates/account/signup-not-found.html new file mode 100644 index 0000000..804510a --- /dev/null +++ b/django_project/igrac/templates/account/signup-not-found.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% load i18n %} +{% load static %} +{% load bootstrap_tags %} +{% load igrac_bootstrap_tags %} + +{% block page_title %} +

Download Form.

+{% endblock page_title %} + +{% block extra_head %} +{% endblock %} + +{% block body_outer %} + +
+ Registration is only for data providers. It is not required to access and download the resources available in the GGIS. If you are from a partner organization and you have data that could be shared in the GGIS, please reach out to the website administrator: ggis@un-igrac.org +
+{% endblock %} + +{% block extra_script %} +{% endblock %} \ No newline at end of file diff --git a/django_project/igrac/templates/account/signup-not-valid.html b/django_project/igrac/templates/account/signup-not-valid.html new file mode 100644 index 0000000..5688572 --- /dev/null +++ b/django_project/igrac/templates/account/signup-not-valid.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% load i18n %} +{% load static %} +{% load bootstrap_tags %} +{% load igrac_bootstrap_tags %} + +{% block page_title %} +

Download Form.

+{% endblock page_title %} + +{% block extra_head %} +{% endblock %} + +{% block body_outer %} + +
+ Please request a valid registration link to the website administrator: ggis@un-igrac.org. +
+{% endblock %} + +{% block extra_script %} +{% endblock %} \ No newline at end of file diff --git a/django_project/igrac/templates/account/signup.html b/django_project/igrac/templates/account/signup.html index e7b63d8..a79ad3e 100644 --- a/django_project/igrac/templates/account/signup.html +++ b/django_project/igrac/templates/account/signup.html @@ -98,7 +98,6 @@

{% trans "Sign up" %}

[^/]+)/metadata_detail/article$', @@ -40,6 +41,21 @@ url(r'^cms/', include(wagtailadmin_urls)), url(r'^wagtail/documents/', include(wagtaildocs_urls)), url(r'^about/', include(wagtail_urls)), + url( + r'^account/signup/not-found', + TemplateView.as_view(template_name='account/signup-not-found.html'), + name='account_signup_not_found' + ), + url( + r'^account/signup/not-valid', + TemplateView.as_view(template_name='account/signup-not-valid.html'), + name='account_signup_not_valid' + ), + url( + r'^account/signup/(?P[^/]+)/', + CustomSignupView.as_view(), + name='account_signup_with_code' + ), url( r'^account/signup/', CustomSignupView.as_view(), diff --git a/django_project/igrac/views.py b/django_project/igrac/views.py index 530dc78..c7a529c 100644 --- a/django_project/igrac/views.py +++ b/django_project/igrac/views.py @@ -1,28 +1,72 @@ +from allauth.account.views import SignupView from django.conf import settings from django.http import Http404 +from django.shortcuts import redirect from django.views.generic import ListView, TemplateView -from allauth.account.views import SignupView - +from geonode.base import register_event +from geonode.documents.models import get_related_documents +from geonode.groups.models import GroupProfile from geonode.maps.models import Map from geonode.maps.views import map_embed -from geonode.groups.models import GroupProfile -from geonode.base import register_event from geonode.monitoring.models import EventType -from geonode.documents.models import get_related_documents - -from igrac.models.map_slug import MapSlugMapping from igrac.forms.signup import SignupWithNameForm +from igrac.models.map_slug import MapSlugMapping +from igrac.models.registration_page import RegistrationPage + + +class RegistrationNotFound(Exception): + pass + + +class RegistrationNotValid(Exception): + pass class CustomSignupView(SignupView): form_class = SignupWithNameForm + def registration_page(self): + """Return registration page.""" + try: + registration_page = RegistrationPage.objects.get( + code=self.kwargs.get('code', '')) + if registration_page.user: + raise RegistrationNotValid() + except RegistrationPage.DoesNotExist: + raise RegistrationNotFound() + return registration_page + + def get(self, request, *args, **kwargs): + """GET File.""" + try: + self.registration_page() + except RegistrationNotFound: + return redirect('account_signup_not_found') + except RegistrationNotValid: + return redirect('account_signup_not_valid') + return super().get(request, *args, **kwargs) + def get_context_data(self, **kwargs): ret = super(CustomSignupView, self).get_context_data(**kwargs) - ret.update({'account_geonode_local_signup': settings.SOCIALACCOUNT_WITH_GEONODE_LOCAL_SINGUP}) + ret.update( + { + 'account_geonode_local_signup': + settings.SOCIALACCOUNT_WITH_GEONODE_LOCAL_SINGUP + } + ) return ret + def get_success_url(self): + """We save the user to Registration page.""" + try: + registration_page = self.registration_page() + registration_page.user = self.user + registration_page.save() + except (AttributeError, RegistrationNotFound, RegistrationNotValid): + pass + return super(CustomSignupView, self).get_success_url() + class HomeView(ListView): model = MapSlugMapping @@ -59,7 +103,8 @@ def get_context_data(self, **kwargs): group = GroupProfile.objects.get(slug=map.group.name) except GroupProfile.DoesNotExist: group = None - site_url = settings.SITEURL.rstrip('/') if settings.SITEURL.startswith('http') else settings.SITEURL + site_url = settings.SITEURL.rstrip('/') if settings.SITEURL.startswith( + 'http') else settings.SITEURL register_event(self.request, EventType.EVENT_VIEW_METADATA, map) context.update({ "resource": map, diff --git a/django_project/version/version.txt b/django_project/version/version.txt index 5f70498..b58299c 100644 --- a/django_project/version/version.txt +++ b/django_project/version/version.txt @@ -1 +1 @@ -4.4.5 \ No newline at end of file +4.4.6 \ No newline at end of file