Model Validation on InlineAdmin in Django - django

Within an app for an online shop I have two simple models for products and deliveries:
class Product(model.models):
delivery = models.ForeignKey(Delivery, on_delete=models.CASCADE)
class Delivery(model.models):
published = models.Datefield()
I am using the build-in Django admin.
class ProductInline(admin.TabularInline):
model = Product
#admin.register(Delivery)
class DeliveryAdmin(admin.ModelAdmin):
inlines = [ProductInline,]
To increase robustness of the app it is very important, that products can't be changed as soon as the related delivery has been published to the customer. So on very attempt to change a product, I need to do some validation to check if the Delivery has been published. Things I have tried:
Create fieldset for InlineAdmin with custom clean()method
Custom clean() method on the model instance
These approaches don't work, though. When implemented, I loose the ability to edit the entire delivery model from the admin panel (I am trying to only limit edits of products). This is because clicking the save Button in the admin Panel will automatically call save() and clean() on the products regardless of weather or not the products were changed.
Has someone encountered a similar problem before?

Maybe, you need to override the form?
class ProductInlineForm(forms.ModelForm):
def clean(self):
# your validation.
# here you can raise ValidationError
return super().clean()
class ProductInline(admin.TabularInline):
form = ProductInlineForm
model = Product
https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#overriding-the-clean-method

Related

Django REST Framework validation function has different signature than Django models?

I'm developing a reservation system REST api in Django with a relatively simple model that will determine if a user has a valid membership in order for them to reserve a certain item. When creating a reservation, I have a simple validator written like this:
def validate_member(value):
"""
Validator for a user in a reservation. Will throw a ValidationError if the user does not have an
updated membership.
"""
if not valid_membership(value):
raise ValidationError("User does not have a valid membership!")
This validation is run on the foreign key field in the reservation table, which is written as such:
# The gear item in the reservation
gear_item = models.ForeignKey(Entry,
on_delete=models.SET_NULL,
null=True,
help_text="The gear item being reserved.",
validators=[validate_entry]
)
The serializer for this model is written as a ModelSerializer, like such:
class ReservationSerializer(serializers.ModelSerializer):
class Meta:
model = Reservation
fields = "__all__"
This design works fine for queries in the REST API, but fails for any modifications in the admin console, with the error: 'int' object has no attribute 'rentable'
It seems that validating a foreign key in the admin console passes the primary key integer into the value parameter of the validation function, while the REST API passes in the entire object. Is there a workaround for this, or should I expect to not use the admin console at all due to this limitation?
Best solution/work around for this would be overriding your admin console's form.
I think this code snippet will be enough for you to handle your specific field and it's validation.

Using database for forms django

I have an area where teachers can put homeworks for students.
Students can see these homeworks and when they click "send homework" button. they can fill a form so Teachers can see the homeworks from admin panel.
I have class to post homework.
I have 4 fields in class to post homework.
Fields = ["student number","Deadline","Lecture","Files"]
when students posting their homeworks, I want only use 2 ["student number","files"] fields for the form and I want other fields ["deadline","Lecture"] to be filled automatically from database.
What is the best way do it?
Quoting django model clean method docs:
Model.clean() This method should be used to provide custom model validation, and to modify attributes on your model if desired. For instance, you could use it to automatically provide a value for a field, or to do validation that requires access to more than a single field.
For your scenario:
import datetime
from django.db import models
class Homeworks(models.Model):
...
def clean(self):
self.Deadline = datetime.date.today() + some delta time
self.Lecture = ...
Also, remove this 'self-filled' fields from admin form interface:
class HomeworksAdmin(admin.ModelAdmin):
fields = ["student number","Files"]

How can I update a model on the Django admin page when the model is click to be viewed?

I have a function that updates my Orders model. In my admin.py, I register the model, and have a admin.ModelAdmin class associated with it. I want to call a function that runs when the registered model is clicked on the admin page, so that it updates before the users sees any information about the orders. I've tried just adding the function:
class OrdersAdmin(admin.ModelAdmin):
...
update_orders()
But this just results in it being updated when I run the server, and not when the "Orders" model is clicked in the admin. I've looked into admin actions, but that is only related to making changes to various fields of the model after the page is populated with objects from Orders Model.
How can I pass a function to be called when I click on the model in the admin page? Thanks.
I guess the best way to do this is to override the get_absolute_url method in your model See docs.
def get_absolute_url(self):
#update you orders here .....
from django.urls import reverse
return reverse('admin:yourapp_yourmodel_change', args=[str(self.id)])

How to validate together-uniqueness of two (foreign key) related fields of a model?

Let's say I have three models in my app that lets user write a review for a book. The requirement is that every user can only do one review for each book.
Review model has two foreign keys to User and Book models. I want to be able to validate user input before creating an instance of Review model to ensure a user can not create more than one review per book.
I know how to do it if I ask the user to provide the User and Book information in the data that's sent to DRF version 3.+.
But the url to post a new Review has the Book id in it, and the user is authenticated:
url used to list and create reviews: /book/{book_id}/reviews/
Right now when I do a POST operation on the url, DRF complains that the book field is required, even though I tried to send keyword arguments to the overloaded perform_create function (see snippets below)
My guess is I should send the information about book and review that's embedded in the url and request to the default UniqueTogetherValidator but I don't know how to do it!
I have seen a partial solution in this question, but my problem is that I not only have to provide the current authenticated user to the validator but also the book information, since I am requiring a unique_together constraint.
I have unsuccessfully tried providing default kwarg to the fields.
Also tried providing a custom validator in the serializer, which didn't succeed.
So my question is how to validate unique_together of input data from user's request when the book information is embedded in the url and the user information is part of the incoming request (while both of those are related foreign keys and not local attributes of Review model)?
Here are my models and serializers:
models.py
class Review(models.Model):
# Relationships
book = models.ForeignKey(Book)
user = models.ForeignKey(User)
comment = models.TextField(null=True, blank=True)
class Meta:
unique_together = (("user", "book"),)
serializers.py
class ReviewSerializer(serializers.ModelSerializer):
book = serializers.PrimaryKeyRelatedField(read_only=True)
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Review
Part of views.py
class ReviewList(generics.ListCreateAPIView):
serializer_class = ReviewSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user, book=self.kwargs['book_id'])
urls.py
url(r'^book/(?P<book_id>\d+)/reviews/$', view=ReviewList.as_view(), name='review-list')
You are trying to make things harder than they should.
My feeling on this issue is that the unique together constraint is part of the business rules. Therefore I'd go with removing that constraint on the serializer. After the serializer has validated the data you'll be able to check the constraint on the review (owner + book) and raise a validation error if there's already one. Doing this in the perform_create before saving the serializer seems sensible.
To remove the unique together constraint you'll have to explicitly set the validators on the serializer:
class ReviewSerializer(serializers.ModelSerializer):
book = serializers.PrimaryKeyRelatedField(read_only=True)
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Review
validators = []
Make sure to print a serializer instance before doing that change so you can make sure you're not removing other contraints.

Django admin order by most recent related model creation

I have some models for a messaging system on my django site.
class Enquiry(models.Model):
etc...
class Message(models.Model):
enquiry = models.ForeignKeyField(Enquiry)
sent_at = models.DateTimeField(default=timezone.now)
etc...
Each message has a foreign key to an enquiry. In the admin site I would like to be able to order the enquiries by the most recent received message. In a regular view, I can do this:
Enquiry.objects.annotate(latest_message=Max('message__sent_at')).order_by('-latest_message')
Is there a way to achieve this in the admin framework?
You can override the get_queryset method for your model admin, and use the same approach that already works in your views.
My first suggestion was to try setting ordering for your model admin, but you reported that it didn't work.
class EnquiryAdmin"
ordering = ['-message__sent_at']