Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds edit, create and delete views #364

Merged
merged 15 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions importing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ def clean(self) -> None:
if not tz:
raise ValidationError("Station must have a timezone set.")

# If the file has changed, we reprocess the data
if self.pk and self.rawfile != self.__class__.objects.get(pk=self.pk).rawfile:
self.reprocess = True

if self.reprocess:
self.status = "N"
self.reprocess = False
11 changes: 10 additions & 1 deletion importing/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
from django.urls import path

from .views import DataImportDetailView, DataImportListView
from .views import (
DataImportCreateView,
DataImportDeleteView,
DataImportDetailView,
DataImportEditView,
DataImportListView,
)

app_name = "importing"
urlpatterns = [
path("", DataImportListView.as_view(), name="dataimport_list"),
path("<int:pk>/", DataImportDetailView.as_view(), name="dataimport_detail"),
path("edit/<int:pk>", DataImportEditView.as_view(), name="dataimport_edit"),
path("create/", DataImportCreateView.as_view(), name="dataimport_create"),
path("delete/<int:pk>", DataImportDeleteView.as_view(), name="dataimport_delete"),
]
34 changes: 31 additions & 3 deletions importing/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
from management.views import CustomDetailView, CustomTableView
from management.views import (
CustomCreateView,
CustomDeleteView,
CustomDetailView,
CustomEditView,
CustomTableView,
)

from .filters import DataImportFilter
from .models import DataImport
Expand All @@ -9,11 +15,33 @@ class DataImportDetailView(CustomDetailView):
"""View to view a data import."""

model = DataImport
show_list_btn = True


class DataImportListView(CustomTableView):
"""View to list all data imports."""

model = DataImport
table_class = DataImportTable
filterset_class = DataImportFilter
show_refresh_btn = True


class DataImportEditView(CustomEditView):
"""View to edit a data import."""

model = DataImport
fields = ["visibility", "station", "format", "rawfile", "reprocess", "observations"]
foreign_key_fields = ["station", "format"]


class DataImportCreateView(CustomCreateView):
"""View to create a data import."""

model = DataImport
fields = ["visibility", "station", "format", "rawfile", "reprocess", "observations"]
foreign_key_fields = ["station", "format"]


class DataImportDeleteView(CustomDeleteView):
"""View to delete a data import."""

model = DataImport
22 changes: 2 additions & 20 deletions management/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from guardian.shortcuts import get_objects_for_user

from .models import User
from .permissions import get_queryset

# Set global preferences for the Django admin site
admin.site.site_header = "Paricia Administration"
Expand Down Expand Up @@ -65,7 +66,7 @@ def get_queryset(self, request):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
"""Limit the queryset for foreign key fields."""
if db_field.name in self.foreign_key_fields:
kwargs["queryset"] = _get_queryset(db_field, request.user)
kwargs["queryset"] = get_queryset(db_field, request.user)
if db_field.name == "owner" and not request.user.is_superuser:
kwargs["initial"] = request.user.id
kwargs["disabled"] = True
Expand All @@ -80,25 +81,6 @@ def formfield_for_choice_field(self, db_field, request, **kwargs):
return super().formfield_for_choice_field(db_field, request, **kwargs)


def _get_queryset(db_field, user):
"""Return a queryset based on the permissions of the user.

Returns queryset of public objects and objects that the user has change permisions
for. For the case of `Station` objects, having the `change` permission is
necessary to include the object in the queryset - being `Public` is not enough.

"""
app_name = db_field.related_model._meta.app_label
model_name = db_field.related_model._meta.model_name
user_objects = get_objects_for_user(user, f"{app_name}.change_{model_name}")
public_objects = (
db_field.related_model.objects.none()
if model_name == "station"
else db_field.related_model.objects.filter(visibility="public")
)
return user_objects | public_objects


class CustomUserAdmin(UserAdmin):
"""A slightly more restrictive user admin page."""

Expand Down
27 changes: 27 additions & 0 deletions management/permissions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Customised permissions."""

from django.db import models as model
from guardian.shortcuts import get_objects_for_user
from rest_framework.permissions import DjangoModelPermissions


Expand All @@ -17,3 +19,28 @@ class CustomDjangoModelPermissions(DjangoModelPermissions):
"PATCH": ["%(app_label)s.change_%(model_name)s"],
"DELETE": ["%(app_label)s.delete_%(model_name)s"],
}


def get_queryset(db_field: model.Field, user: model.Model) -> model.QuerySet:
"""Return a queryset based on the permissions of the user.

Returns queryset of public objects and objects that the user has change permisions
for. For the case of `Station` objects, having the `change` permission is
necessary to include the object in the queryset - being `Public` is not enough.

Args:
db_field (model.Field): Field to filter.
user (model.Model): User to check permissions for.

Returns:
model.QuerySet: Queryset of objects that the user has permissions for.
"""
app_name = db_field.related_model._meta.app_label
model_name = db_field.related_model._meta.model_name
user_objects = get_objects_for_user(user, f"{app_name}.change_{model_name}")
public_objects = (
db_field.related_model.objects.none()
if model_name == "station"
else db_field.related_model.objects.filter(visibility="public")
)
return user_objects | public_objects
41 changes: 41 additions & 0 deletions management/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from django.contrib.admin.utils import NestedObjects
from django.db import models
from django.utils.encoding import force_str
from django.utils.text import capfirst


def get_deleted_objects(
objs: list[models.Model],
) -> tuple[list[str], dict[str, int], list[str]]:
"""Return information about related objects to be deleted.

How to do this has been taken from https://stackoverflow.com/a/39533619/3778792

Args:
objs (list[models.Model]): List of objects to be deleted.

Returns:
tuple[list[str], dict[str, int], list[str]]: Tuple containing the following:
- List of strings representing the objects to be deleted.
- Dictionary containing the count of objects to be deleted for each model.
- List of strings representing the objects that are protected from deletion
"""
collector = NestedObjects(using="default")
collector.collect(objs)

def format_callback(obj):
opts = obj._meta
no_edit_link = f"{capfirst(opts.verbose_name)}: {force_str(obj)}"
return no_edit_link

to_delete = collector.nested(format_callback)
protected = [format_callback(obj) for obj in collector.protected]
model_count = {
model._meta.verbose_name_plural: len(objs)
for model, objs in collector.model_objs.items()
}
if len(to_delete) == 0:
to_delete.append("None")
if len(protected) == 0:
protected.append("None")
return to_delete, model_count, protected
Loading