Django REST - Trigger action when updating a model in a REST-friendly way - django

I have the following use case that I want to implement:
an LMS app powered by DRF has an Exam model, which has a state field which can either be DRAFT or PUBLISHED.
When a teacher publishes an exam, I want them to have the option to also create an announcement on the course (there a model called Announcement for that).
I'm looking for an elegant and efficient way to encode and utilize the information of whether the user wants to contextually publish an announcement when publishing an exam.
This requires identifying three steps:
how to encode whether the user wants to publish the annoncement in a REST request to publish the exam
how to intercept when an exam is being published to optionally publish an exam
how to pass the information about whether the user wants to publish an announcement over to whichever part of the application will be responsbile for actually publishing it.
For the second step, I decided to go with the django-lifecycle package.This beatiful package allows defining a method on the Exam model class itself to look something like:
#hook(AFTER_UPDATE, when='state', is_now=PUBLISHED)
def on_publish(self):
# ... logic
As per the rest, here's two possible solutions I've thought of, which I'd like some feedback on:
define a boolean field publish_announcement on Exam. Inside of the update request for the exam, together with the other payload fields, the frontend will be able to either set that to true or false. Then, in the handler for the exam update, I can do something like:
#hook(AFTER_UPDATE, when='state', is_now=PUBLISHED)
def on_publish(self):
if self.publish_announcement:
publish_announcement(...)
self.publish_announcement = False
self.save()
The upside of this method is that it's trivially easy to pass the information of whether, for a given update request, the user wants to trigger the task of publishing an announcement---all they have to do is put that into the PUT/PATCH request made to the exam. There's also no additional code to handle that information as it's just a field on the model.
The downside is that I'm actually using up a column to store something that isn't a db information at all. As you can see, I'm resetting the value of that field as soon as I'm done updating (this is done to prevent subsequent updates to unintentionally trigger announcement publishing): all I really want to do is pass that information to the AFTER_UPDATE hook and then throw it away.
use a field on the Exam model class which isn't actually a Django field. The idea is that, when receiving an update request, the user could state they want to trigger announcement publishing via, say, a query param. The view would check for the existence of this query and, if truthy, would set a field on the Exam model. The difference here is that the field isn't saved to the db, it's just a class instance field created on the fly and thrown away as the object is disposed of. The hook method could just test that field similarly, this time without having to reset its value at the end because the model doesn't have such a field.
This has the advantage of not having to create db fields which aren't used as real fields, but it adds the need for some "glue" code that adds the temporary field on the model based on the request params.
Can you think of a better alternative?

Related

Saving ModelForm progress values to session in Django

I have flow where users can create (model) forms. If form is valid, object gets saved and flow continues, but selection is in multiple pages. I need to keep current state of created object, which cannot be saved before it's completely valid.
One thing I can do is to always pass things around those views in the ModelForm to make sure, that user never loses data, but I also wanna make sure, that if he leaves the flow and comes back, he doesn't lose data, that he already entered previously.
That's why I decided I wanna save all the fields to session.
Is this correct approach?
How would you do this?
Where would you put this session logic?
What's best way of getting the fields from incomplete form to be saved?
Edit:
Please don't give me advice on how to use session, I am talking more about high level logic and architecture than specific implementation.
I should describe my flow a bit more. Model has 3 fields.
normal dropdown (foreign key referencing another model)
textfield
another foreign key, but this time not done by select, but it's own separate page with lots of filters to help user pick the right (foreign) model
Flow is not linear, because user can start in different parts of page.
Sometimes user can go to page, where he has first 2 fields + button "Browse", which takes you to selection page for 3rd field. Then after he selects field there, he comes back.
But sometimes he selects first this field and then comes to screen with 2 remaining fields, where he needs to fill those.
django-formtools offers a great way to do this using Form wizard.
The form wizard application splits forms across multiple Web pages. It
maintains state in one of the backends so that the full server-side
processing can be delayed until the submission of the final form.
More info here https://django-formtools.readthedocs.io/en/latest/wizard.html
to save in session:
request.session["variable_name"] = "value"
to get from session request.session["variable_name"]. sure you can use request.session.get("..") in both too

See if Django form submission came from the admin

I'm new to django, so I apologize if this has been asked. I'm using the post_save signal to run a task when a new object is created. I need to be able to check if the form was submitted from the admin page or if it was submitted on the live website, is this possible? Where might I find documentation on this?
post_save is too late in the process to log or take action on the source of the object.
post_save is a signal sent from the database/ORM, i.e. it is what is called after a save is done. Does the save function take any input about the source? No. Does that function put anything into the ORM or the database about the source? No.
You want to take whatever action it is in the view function where this occurs. Here is the simplest way to do it that I can think of. I shall assume that your preferred choice of action is to save where it was created in the database.
Consider the following:
class YourObject(models.Model):
name = models.CharField(max_length=30)
creation_location = models.CharField(max_length=30, default="Admin")
Here we have an object with a name and a creation_location, the purpose of the second being to tell where the object is created. To prevent any need to edit the Admin functionality, as that can be a pain, the default value is set to Admin.
Onto the view:
def create_model_view(request):
your_object = YourObject.objects.create(name='FirstObject', creation_location="View")
Here we have a view with a create function for the object. In the initialization, the default value of Admin for the creation_location is overwritten and set to View.

How to save a related, inline django model when parent is saved in the admin?

In my models, I have an Event class, a Volunteer class, and a Session class. The Session class has a foreign key field for an Event and a Volunteer, and is a unique coupling of both, as well as a date and time. Taken together, Volunteer and Event I think technically have a ManyToMany relationship.
Using the pre-packaged Django admin, I edit Volunteers and Events with their own admin.ModelAdmin classes respectively. Sessions are edited inline in the Events ModelAdmin.
When I add a new Session to an event in the admin interface, with a Volunteer, I need the Volunteer's hours field to be automatically updated, to reflect however many hours the newly added session lasted (plus all past sessions). Currently, I just have a calculate_hours function in the Volunteer model, which iterates over all sessions each time it is called and finds the sum of the hours. I tried to call it with a custom save function in Session, but it appears never to be called after the Event save function. I would try it in Event, but I have no way to isolate which Volunteers need their hours recalculated. The hours field IS updated if I manually go over to the Volunteer admin page, edit, and then save the Volunteer, but this is pretty unacceptable.
I see that there are many questions on SO about Django problems when saving inline objects on the admin site, particularly with ManyToMany fields. I'm not sure, after reading many of these questions, if what they say applies in my case--maybe I need to receive a signal somewhere, or include a custom save in a special place, or call save_model in my admin.ModelAdmin class... I just don't know. What is the best way to go about this?
Code can be found here: Models.py, Admin.py
First of all, the relationship you're describing is what called a ManyToMany "through" (you can read about it in the documentation here).
Secondly, I don't understand why you need the 'hours' to be a field at all. Isn't a function enough for this? why save it in the database in the first place? you can just call it every time you need it.
Finally, it seems to me you're doing a lot of extra work that I don't understand - why do you need the volunteer time boolean field? If you link a volunteer with an event isn't that enough to know that he was there? And what's the purpose of "counts_towards_volunteer_time"? I'm probably missing some of the logic here, but a lot of that seems wasteful.

Detect which fields change in a Django ModelForm

I have an app where user submitted data needs to go through a verification process before it shows up on the site. At the moment this means they cannot edit the item without removing it from the site (so our admins can check it's okay).
I'd like to write another model where I can store revisions. Basically three fields where I store the date submitted, a boolean saying if the user is ready for that revision to be considered and a third where I store all the changes (as a pickled/JSON dict).
The problem I have at the moment is I don't want to bombard the admins with a complete listing each time. I only want them to see the changed fields. This means I need a way of generating a list of which fields have changed when the user submits the edit ModelForm so I only save this data in the revision.
There are probably several ways of doing this but my post-pub-quiz brain is slightly numb and can't think of the best way. How would you do it?
In terms of where this would go, I'd probably write it as an abstract ModelForm-inheriting class that other forms use. I'd override save() to stop it writing the data directly back to database (I'd want to redirect it through this fancy new revisions model).
Come to think of it, is there an app that already does this generically?

What kind of validations should I use in my db models?

My form validators are pretty good, and if a form passes is_valid, all data should be ok to insert in the db. Should I still validate something on the db model? What else could there be validated on the db side? Because right now, except maybe for uniqueness ( which I can't do from my FormModel ), I can't think of anything else.
EDIT:
I did some work with Rails earlier, and there you would validate a form on the client side, using JS, and on the server side using model validations. I saw in django you can validate on the client side, using JS, and on the server side you have 2 validation checks: forms and models. This is what confused me.
All data should be validated in the database if possible whether you validate from the front end or not. The first validation should be the datatype, for instance using a date datatype will ensure that no nondates can ever get into your database. If you have relationships between tables these absolutely must be enforced at the database level. If the data must be unique, it is irresponsible to not put a unique index on it. If you have a distinct set of values that are the only ones allowed, then put them in a lookup table and add a forign key constraint to that table.
The reason why it is CRITICAL to do validations in the database itself is that the user interface will not be the only thing that interacts with the database (even if you think it will be). Other applications may do so, people will need to make data changes through imports or at a query window (to fix/change large amounts of data such as when client a buys client B and you need to convert all the data to client A). Also if you change the application interface you might lose the some of the critical data integrity checks in the rewrite. Data integrity is one of the most critical factors in database design and maintenance. If you can't count on data integrity, you have no data. I have never seen a database that lets this stuff be handled by the application that didn't lose data integrity over time. Remember the database will far outlast the current application. People will still be looking at this data for years to come. The application typically doesn't consider reporting which is where the data integrity problems tend to come to light. You don't want to have to explain why you have 10,000,000 in orders that you can't identify who they were shipped to, for instance.
If your data has a constraint that's always valid, you should force it in the model/database level (and optionally at the form level). Your DB can be input in multiple ways besides just a form where validation was checked. E.g., someone can go to the django shell to save models directly or someone could create/edit a model in the admin interface or some later designer creates a new form somehow, that doesn't validate correctly.
Granted this is only required if there are additional constraints on the data. Django automatically will validate for things like fields storing proper values, if you are using the correct field types. E.g., IntegerField validates to ensure it contains an integer, EmailField checks that its entered in the form of a valid email address, django.contrib.localflavor.us.models.PhoneNumberField is a US phone number, etc. Note, this only happens if your models have the proper fields (e.g., if you use CharFields for email addresses no validation can be performed.
But there may be other links between data structures, where you should write your own validation. E.g., if all custom orders requiring special instructions (and non-custom orders only sometimes have special instructions), you should check to enforce all custom orders have something in the special instructions field (and maybe have some minimum length).
EDIT: In response to your edit, the reason for three potential validations in django is straightforward -- different validations at different points for different reasons.
Client side (javascript/jquery) validation can't be trusted at all, and should only be given as a convenience for users almost as an afterthought (if you want a spiffy smooth interface). AFAIK, django doesn't have JS validation unless you use an external package like django-ajax-forms or something, but you don't trust that the validation is correct.
Second, there's a difference between form and model validation. One model may have multiple forms for different purposes. For example, you may have a blog with a Comment Model and allow two types of users to comment: signed in users, or anonymous users. The form for anonymous users may require giving a name/email before they comment, while the form for logged in users doesn't need those fields. The signed in user form, when processed in a view may automatically add the correct name and email addresses of the signed in user to the comment model before being saved.
In contrast, model validation always applies and will always be true at the database level, regardless of how they tried saving the data. If you want to make sure some condition always applies make sure it is at the DB level. (And you don't have to write put that validation in at the form level).