Merge pull request 'Update docker' (#159) from master into Docker
Reviewed-on: SGD-Nord/konova#159
This commit is contained in:
@@ -8,6 +8,7 @@ Created on: 22.07.21
|
||||
from django.contrib import admin
|
||||
|
||||
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup
|
||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
|
||||
from user.models import UserAction
|
||||
|
||||
@@ -16,7 +17,22 @@ class GeometryAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"created",
|
||||
"st_area",
|
||||
]
|
||||
readonly_fields = [
|
||||
"st_area",
|
||||
"created",
|
||||
"modified",
|
||||
]
|
||||
|
||||
def st_area(self, obj):
|
||||
val = None
|
||||
geom = obj.geom
|
||||
if geom is not None:
|
||||
geom.transform(ct=DEFAULT_SRID_RLP)
|
||||
val = geom.area
|
||||
return val
|
||||
st_area.short_description = f"Area (srid={DEFAULT_SRID_RLP})"
|
||||
|
||||
|
||||
class ParcelAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -52,14 +52,16 @@ class InterventionAutocomplete(Select2QuerySetView):
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
user = self.request.user
|
||||
if user.is_anonymous:
|
||||
return Intervention.objects.none()
|
||||
qs = Intervention.objects.filter(
|
||||
deleted=None,
|
||||
users__in=[self.request.user],
|
||||
Q(deleted=None) &
|
||||
Q(users__in=[user]) |
|
||||
Q(teams__in=user.teams.all())
|
||||
).order_by(
|
||||
"identifier"
|
||||
)
|
||||
).distinct()
|
||||
if self.q:
|
||||
qs = qs.filter(
|
||||
Q(identifier__icontains=self.q) |
|
||||
|
||||
@@ -57,6 +57,8 @@ class BaseForm(forms.Form):
|
||||
self.has_required_fields = True
|
||||
break
|
||||
|
||||
self.check_for_recorded_instance()
|
||||
|
||||
@abstractmethod
|
||||
def save(self):
|
||||
# To be implemented in subclasses!
|
||||
@@ -136,6 +138,38 @@ class BaseForm(forms.Form):
|
||||
set_class = set_class.replace(cls, "")
|
||||
self.fields[field].widget.attrs["class"] = set_class
|
||||
|
||||
def check_for_recorded_instance(self):
|
||||
""" Checks if the instance is recorded and runs some special logic if yes
|
||||
|
||||
If the instance is recorded, the form shall not display any possibility to
|
||||
edit any data. Instead, the users should get some information about why they can not edit anything.
|
||||
|
||||
There are situations where the form should be rendered regularly,
|
||||
e.g deduction forms for (recorded) eco accounts.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
|
||||
RemoveEcoAccountDeductionModalForm
|
||||
is_none = self.instance is None
|
||||
is_other_data_type = not isinstance(self.instance, BaseObject)
|
||||
is_deduction_form = isinstance(
|
||||
self,
|
||||
(
|
||||
NewDeductionModalForm,
|
||||
EditEcoAccountDeductionModalForm,
|
||||
RemoveEcoAccountDeductionModalForm,
|
||||
)
|
||||
)
|
||||
|
||||
if is_none or is_other_data_type or is_deduction_form:
|
||||
# Do nothing
|
||||
return
|
||||
|
||||
if self.instance.is_recorded:
|
||||
self.template = "form/recorded_no_edit.html"
|
||||
|
||||
|
||||
class RemoveForm(BaseForm):
|
||||
check = forms.BooleanField(
|
||||
@@ -410,7 +444,6 @@ class NewDocumentModalForm(BaseModalForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Add new document")
|
||||
self.form_caption = _("")
|
||||
self.template = "modal/modal_form.html"
|
||||
self.form_attrs = {
|
||||
"enctype": "multipart/form-data", # important for file upload
|
||||
}
|
||||
@@ -597,4 +630,12 @@ class RecordModalForm(BaseModalForm):
|
||||
self.instance.set_unrecorded(self.user)
|
||||
else:
|
||||
self.instance.set_recorded(self.user)
|
||||
return self.instance
|
||||
return self.instance
|
||||
|
||||
def check_for_recorded_instance(self):
|
||||
""" Overwrite the check method for doing nothing on the RecordModalForm
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -5,6 +5,10 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 04.01.22
|
||||
|
||||
"""
|
||||
import datetime
|
||||
|
||||
from django.contrib.gis.db.models.functions import Area
|
||||
|
||||
from konova.management.commands.setup import BaseKonovaCommand
|
||||
from konova.models import Geometry, Parcel, District
|
||||
|
||||
@@ -23,12 +27,21 @@ class Command(BaseKonovaCommand):
|
||||
num_parcels_before = Parcel.objects.count()
|
||||
num_districts_before = District.objects.count()
|
||||
self._write_warning("=== Update parcels and districts ===")
|
||||
# Order geometries by size to process smaller once at first
|
||||
geometries = Geometry.objects.all().exclude(
|
||||
geom=None
|
||||
).annotate(area=Area("geom")).order_by(
|
||||
'area'
|
||||
)
|
||||
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
|
||||
i = 0
|
||||
num_geoms = geometries.count()
|
||||
for geometry in geometries:
|
||||
self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...")
|
||||
geometry.update_parcels()
|
||||
self._write_warning(f"--- Processed {geometry.get_underlying_parcels().count()} underlying parcels")
|
||||
i += 1
|
||||
self._write_warning(f"--- {i}/{num_geoms} processed")
|
||||
|
||||
num_parcels_after = Parcel.objects.count()
|
||||
num_districts_after = District.objects.count()
|
||||
|
||||
@@ -20,6 +20,9 @@ class Geometry(BaseResource):
|
||||
from konova.settings import DEFAULT_SRID
|
||||
geom = MultiPolygonField(null=True, blank=True, srid=DEFAULT_SRID)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
self.check_for_conflicts()
|
||||
@@ -110,32 +113,38 @@ class Geometry(BaseResource):
|
||||
_now = timezone.now()
|
||||
underlying_parcels = []
|
||||
for result in fetched_parcels:
|
||||
fetched_parcel = result[typename]
|
||||
parcel_properties = result["properties"]
|
||||
# There could be parcels which include the word 'Flur',
|
||||
# which needs to be deleted and just keep the numerical values
|
||||
## THIS CAN BE REMOVED IN THE FUTURE, WHEN 'Flur' WON'T OCCUR ANYMORE!
|
||||
flr_val = fetched_parcel["ave:flur"].replace("Flur ", "")
|
||||
flr_val = parcel_properties["flur"].replace("Flur ", "")
|
||||
district = District.objects.get_or_create(
|
||||
key=fetched_parcel["ave:kreisschl"],
|
||||
name=fetched_parcel["ave:kreis"],
|
||||
key=parcel_properties["kreisschl"],
|
||||
name=parcel_properties["kreis"],
|
||||
)[0]
|
||||
municipal = Municipal.objects.get_or_create(
|
||||
key=fetched_parcel["ave:gmdschl"],
|
||||
name=fetched_parcel["ave:gemeinde"],
|
||||
key=parcel_properties["gmdschl"],
|
||||
name=parcel_properties["gemeinde"],
|
||||
district=district,
|
||||
)[0]
|
||||
parcel_group = ParcelGroup.objects.get_or_create(
|
||||
key=fetched_parcel["ave:gemaschl"],
|
||||
name=fetched_parcel["ave:gemarkung"],
|
||||
key=parcel_properties["gemaschl"],
|
||||
name=parcel_properties["gemarkung"],
|
||||
municipal=municipal,
|
||||
)[0]
|
||||
flrstck_nnr = parcel_properties['flstnrnen']
|
||||
if not flrstck_nnr:
|
||||
flrstck_nnr = None
|
||||
flrstck_zhlr = parcel_properties['flstnrzae']
|
||||
if not flrstck_zhlr:
|
||||
flrstck_zhlr = None
|
||||
parcel_obj = Parcel.objects.get_or_create(
|
||||
district=district,
|
||||
municipal=municipal,
|
||||
parcel_group=parcel_group,
|
||||
flr=flr_val,
|
||||
flrstck_nnr=fetched_parcel['ave:flstnrnen'],
|
||||
flrstck_zhlr=fetched_parcel['ave:flstnrzae'],
|
||||
flrstck_nnr=flrstck_nnr,
|
||||
flrstck_zhlr=flrstck_zhlr,
|
||||
)[0]
|
||||
parcel_obj.district = district
|
||||
parcel_obj.updated_on = _now
|
||||
|
||||
@@ -289,6 +289,8 @@ class RecordableObjectMixin(models.Model):
|
||||
from user.models import UserActionLogEntry
|
||||
if self.recorded:
|
||||
return None
|
||||
|
||||
self.unshare_with_default_users()
|
||||
action = UserActionLogEntry.get_recorded_action(user)
|
||||
self.recorded = action
|
||||
self.save()
|
||||
@@ -335,6 +337,15 @@ class RecordableObjectMixin(models.Model):
|
||||
"""
|
||||
raise NotImplementedError("Implement this in the subclass!")
|
||||
|
||||
@property
|
||||
def is_recorded(self):
|
||||
""" Getter for record status as property
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.recorded is not None
|
||||
|
||||
|
||||
class CheckableObjectMixin(models.Model):
|
||||
# Checks - Refers to "Genehmigen" but optional
|
||||
@@ -608,6 +619,26 @@ class ShareableObjectMixin(models.Model):
|
||||
"""
|
||||
raise NotImplementedError("Must be implemented in subclasses!")
|
||||
|
||||
def unshare_with_default_users(self):
|
||||
""" Removes all shared users from direct shared access which are only default group users
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
from konova.utils.user_checks import is_default_group_only
|
||||
users = self.shared_users
|
||||
cleaned_users = []
|
||||
default_users = []
|
||||
for user in users:
|
||||
if not is_default_group_only(user):
|
||||
cleaned_users.append(user)
|
||||
else:
|
||||
default_users.append(user)
|
||||
self.share_with_user_list(cleaned_users)
|
||||
|
||||
for user in default_users:
|
||||
celery_send_mail_shared_access_removed.delay(self.identifier, self.title, user.id)
|
||||
|
||||
|
||||
class GeoReferencedMixin(models.Model):
|
||||
geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
@@ -19,6 +19,6 @@ PAGE_SIZE_OPTIONS_TUPLES = [
|
||||
(50, 50),
|
||||
(100, 100),
|
||||
]
|
||||
PAGE_SIZE_DEFAULT = 5
|
||||
PAGE_SIZE_DEFAULT = 10
|
||||
PAGE_SIZE_MAX = 100
|
||||
PAGE_DEFAULT = 1
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{% load l10n i18n %}
|
||||
{% for parcel in parcels %}
|
||||
{% if forloop.last and next_page %}
|
||||
<tr hx-get="{% url 'geometry-parcels-content' geom_id next_page %}"
|
||||
hx-trigger="intersect once"
|
||||
hx-swap="afterend">
|
||||
<td>{{parcel.parcel_group.name|default_if_none:"-"}}</td>
|
||||
<td>{{parcel.parcel_group.key|default_if_none:"-"}}</td>
|
||||
<td>{{parcel.flr|default_if_none:"-"|unlocalize}}</td>
|
||||
<td>{{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}}</td>
|
||||
<td>{{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td>{{parcel.parcel_group.name|default_if_none:"-"}}</td>
|
||||
<td>{{parcel.parcel_group.key|default_if_none:"-"}}</td>
|
||||
<td>{{parcel.flr|default_if_none:"-"|unlocalize}}</td>
|
||||
<td>{{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}}</td>
|
||||
<td>{{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -37,16 +37,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for parcel in parcels %}
|
||||
<tr>
|
||||
<td>{{parcel.parcel_group.name|default_if_none:"-"}}</td>
|
||||
<td>{{parcel.parcel_group.key|default_if_none:"-"}}</td>
|
||||
<td>{{parcel.flr|default_if_none:"-"|unlocalize}}</td>
|
||||
<td>{{parcel.flrstck_zhlr|default_if_none:"-"|unlocalize}}</td>
|
||||
<td>{{parcel.flrstck_nnr|default_if_none:"-"|unlocalize}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% include 'konova/includes/parcels/parcel_table_content.html' %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
@@ -8,7 +8,7 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div hx-trigger="every 2s" hx-get="{% url 'geometry-parcels' geom_form.instance.geometry.id %}">
|
||||
<div hx-trigger="load, every 5s" hx-get="{% url 'geometry-parcels' geom_form.instance.geometry.id %}">
|
||||
<div class="row justify-content-center">
|
||||
<span class="spinner-border rlp-r-inv" role="status"></span>
|
||||
</div>
|
||||
19
konova/templates/konova/includes/report/qrcodes.html
Normal file
19
konova/templates/konova/includes/report/qrcodes.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<button class="btn btn-outline-default col-sm-12">
|
||||
<a href="{{qrcode.url}}" target="_blank">
|
||||
<h4>{% trans 'Open in browser' %}</h4>
|
||||
{{ qrcode.img|safe }}
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<button class="btn btn-outline-default col-sm-12">
|
||||
<a href="{{qrcode_lanis.url}}" target="_blank">
|
||||
<h4>{% trans 'View in LANIS' %}</h4>
|
||||
{{ qrcode_lanis.img|safe }}
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
@@ -24,7 +24,7 @@ from konova.autocompletes import EcoAccountAutocomplete, \
|
||||
ShareTeamAutocomplete, HandlerCodeAutocomplete
|
||||
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||
from konova.sso.sso import KonovaSSOClient
|
||||
from konova.views import logout_view, home_view, get_geom_parcels
|
||||
from konova.views import logout_view, home_view, get_geom_parcels, get_geom_parcels_content
|
||||
|
||||
sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
|
||||
urlpatterns = [
|
||||
@@ -40,7 +40,8 @@ urlpatterns = [
|
||||
path('cl/', include("codelist.urls")),
|
||||
path('analysis/', include("analysis.urls")),
|
||||
path('api/', include("api.urls")),
|
||||
path('geom/<id>/parcels', get_geom_parcels, name="geometry-parcels"),
|
||||
path('geom/<id>/parcels/', get_geom_parcels, name="geometry-parcels"),
|
||||
path('geom/<id>/parcels/<int:page>', get_geom_parcels_content, name="geometry-parcels-content"),
|
||||
|
||||
# Autocomplete paths for all apps
|
||||
path("atcmplt/eco-accounts", EcoAccountAutocomplete.as_view(), name="accounts-autocomplete"),
|
||||
|
||||
@@ -17,6 +17,7 @@ IDENTIFIER_REPLACED = _("The identifier '{}' had to be changed to '{}' since ano
|
||||
ENTRY_REMOVE_MISSING_PERMISSION = _("Only conservation or registration office users are allowed to remove entries.")
|
||||
MISSING_GROUP_PERMISSION = _("You need to be part of another user group.")
|
||||
CHECKED_RECORDED_RESET = _("Status of Checked and Recorded reseted")
|
||||
RECORDED_BLOCKS_EDIT = _("Entry is recorded. To edit data, the entry first needs to be unrecorded.")
|
||||
|
||||
# SHARE
|
||||
DATA_UNSHARED = _("This data is not shared with you")
|
||||
|
||||
@@ -5,11 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 17.12.21
|
||||
|
||||
"""
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from json import JSONDecodeError
|
||||
from time import sleep
|
||||
|
||||
import requests
|
||||
import xmltodict
|
||||
from django.contrib.gis.db.models.functions import AsGML, Transform
|
||||
from requests.auth import HTTPDigestAuth
|
||||
|
||||
@@ -115,7 +116,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
|
||||
geometry_operation,
|
||||
filter_srid
|
||||
)
|
||||
_filter = f'<wfs:GetFeature service="WFS" version="{self.version}" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:myns="http://www.someserver.com/myns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0.0/wfs.xsd" count="{self.count}" startindex="{start_index}"><wfs:Query typeNames="{typenames}">{spatial_filter}</wfs:Query></wfs:GetFeature>'
|
||||
_filter = f'<wfs:GetFeature service="WFS" version="{self.version}" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:myns="http://www.someserver.com/myns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0.0/wfs.xsd" count="{self.count}" startindex="{start_index}" outputFormat="application/json; subtype=geojson"><wfs:Query typeNames="{typenames}">{spatial_filter}</wfs:Query></wfs:GetFeature>'
|
||||
return _filter
|
||||
|
||||
def get_features(self,
|
||||
@@ -139,7 +140,7 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
|
||||
Returns:
|
||||
features (list): A list of returned features
|
||||
"""
|
||||
features = []
|
||||
found_features = []
|
||||
while start_index is not None:
|
||||
post_body = self._create_post_data(
|
||||
spatial_operator,
|
||||
@@ -155,19 +156,11 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
|
||||
)
|
||||
|
||||
content = response.content.decode("utf-8")
|
||||
content = xmltodict.parse(content)
|
||||
collection = content.get(
|
||||
"wfs:FeatureCollection",
|
||||
{},
|
||||
)
|
||||
|
||||
# Check if collection is an exception and does not contain the requested data
|
||||
if len(collection) == 0:
|
||||
exception = content.get(
|
||||
"ows:ExceptionReport",
|
||||
{}
|
||||
)
|
||||
if len(exception) > 0 and rerun_on_exception:
|
||||
try:
|
||||
# Check if collection is an exception and does not contain the requested data
|
||||
content = json.loads(content)
|
||||
except JSONDecodeError as e:
|
||||
if rerun_on_exception:
|
||||
# Wait a second before another try
|
||||
sleep(1)
|
||||
self.get_features(
|
||||
@@ -177,22 +170,21 @@ class ParcelWFSFetcher(AbstractWFSFetcher):
|
||||
start_index,
|
||||
rerun_on_exception=False
|
||||
)
|
||||
|
||||
members = collection.get(
|
||||
"wfs:member",
|
||||
None,
|
||||
)
|
||||
if members is not None:
|
||||
if len(members) > 1:
|
||||
# extend feature list with found list of new feature members
|
||||
features += members
|
||||
else:
|
||||
# convert single found feature member into list and extent feature list
|
||||
features += [members]
|
||||
e.msg += content
|
||||
raise e
|
||||
fetched_features = content.get(
|
||||
"features",
|
||||
{},
|
||||
)
|
||||
|
||||
if collection.get("@next", None) is not None:
|
||||
start_index += self.count
|
||||
else:
|
||||
found_features += fetched_features
|
||||
|
||||
if len(fetched_features) < self.count:
|
||||
# The response was not 'full', so we got everything to fetch
|
||||
start_index = None
|
||||
else:
|
||||
# If a 'full' response returned, there might be more to fetch. Increase the start_index!
|
||||
start_index += self.count
|
||||
|
||||
return features
|
||||
return found_features
|
||||
|
||||
@@ -110,12 +110,12 @@ def get_geom_parcels(request: HttpRequest, id: str):
|
||||
id (str): The geometry's id
|
||||
|
||||
Returns:
|
||||
|
||||
A rendered piece of HTML
|
||||
"""
|
||||
# HTTP code 286 states that the HTMX should stop polling for updates
|
||||
# https://htmx.org/docs/#polling
|
||||
status_code = 286
|
||||
template = "konova/includes/parcel_table.html"
|
||||
template = "konova/includes/parcels/parcel_table_frame.html"
|
||||
geom = get_object_or_404(Geometry, id=id)
|
||||
parcels = geom.get_underlying_parcels()
|
||||
geos_geom = geom.geom
|
||||
@@ -133,9 +133,18 @@ def get_geom_parcels(request: HttpRequest, id: str):
|
||||
parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr")
|
||||
municipals = parcels.order_by("municipal").distinct("municipal").values("municipal__id")
|
||||
municipals = Municipal.objects.filter(id__in=municipals)
|
||||
|
||||
rpp = 100
|
||||
parcels = parcels[:rpp]
|
||||
next_page = 1
|
||||
if len(parcels) < rpp:
|
||||
next_page = None
|
||||
|
||||
context = {
|
||||
"parcels": parcels,
|
||||
"municipals": municipals,
|
||||
"geom_id": str(id),
|
||||
"next_page": next_page,
|
||||
}
|
||||
html = render_to_string(template, context, request)
|
||||
return HttpResponse(html, status=status_code)
|
||||
@@ -143,6 +152,49 @@ def get_geom_parcels(request: HttpRequest, id: str):
|
||||
return HttpResponse(None, status=404)
|
||||
|
||||
|
||||
@login_required
|
||||
def get_geom_parcels_content(request: HttpRequest, id: str, page: int):
|
||||
""" Getter for infinite scroll of HTMX
|
||||
|
||||
Returns parcels of a specific page/slice of the found parcel set.
|
||||
Implementation of infinite scroll htmx example: https://htmx.org/examples/infinite-scroll/
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
id (str): The geometry's id
|
||||
page (int): The requested page number
|
||||
|
||||
Returns:
|
||||
A rendered piece of HTML
|
||||
"""
|
||||
if page < 0:
|
||||
raise AssertionError("Parcel page can not be negative")
|
||||
|
||||
# HTTP code 286 states that the HTMX should stop polling for updates
|
||||
# https://htmx.org/docs/#polling
|
||||
status_code = 286
|
||||
template = "konova/includes/parcels/parcel_table_content.html"
|
||||
geom = get_object_or_404(Geometry, id=id)
|
||||
parcels = geom.get_underlying_parcels()
|
||||
|
||||
parcels = parcels.order_by("-municipal", "flr", "flrstck_zhlr", "flrstck_nnr")
|
||||
rpp = 100
|
||||
from_p = rpp * (page-1)
|
||||
to_p = rpp * (page)
|
||||
next_page = page + 1
|
||||
parcels = parcels[from_p:to_p]
|
||||
if len(parcels) < rpp:
|
||||
next_page = None
|
||||
|
||||
context = {
|
||||
"parcels": parcels,
|
||||
"geom_id": str(id),
|
||||
"next_page": next_page,
|
||||
}
|
||||
html = render_to_string(template, context, request)
|
||||
return HttpResponse(html, status=status_code)
|
||||
|
||||
|
||||
def get_404_view(request: HttpRequest, exception=None):
|
||||
""" Returns a 404 handling view
|
||||
|
||||
|
||||
Reference in New Issue
Block a user