Customize (override) Flask-Admin's Submit method from edit view - python

Preconditions:
I'm new to Python and to Flask-Admin in particular. I created a simple test service, which has MondoDB, keeping the data with relationship of 'one-to-one' kind.
employeeName -> salary
The model looks like that:
class Employee(db.Document):
fullName = db.StringField(max_length=160, unique=True)
salary = db.IntField()
And I use Flask-Admin to observe the table with the data and to edit it.
When I want to change the 'salary' field, I just press the 'edit' button and in Flask-Admin's default edit view I change the integer value. I press 'Submit' and a new value in the database is successfully applied.
Question:
But I need to override the Submit method in the way, that leaves as it is the functionality and adds some custom code. Like let's assume I want to add a comment in the log file after an actual db submit:
logging.warning('The salary of %s: was changed to /%s', fullName, salary)
Any suggestion on how to achieve that would be much appreciated. Perhaps you could direct me in the way to go, since the Flask-Admin documentation doesn't give me enough help so far.

You can override on_model_change method to add your custom logic. Check http://flask-admin.readthedocs.org/en/latest/api/mod_model/#flask.ext.admin.model.BaseModelView.on_model_change

I ended up overriding a save method in my Document-derived class.
So now my Employee class contains this kind of code:
def save(self, *args, **kwargs):
print 'whatever I want to do myself is here'
return super(Employee, self).save(*args, **kwargs)
Today I found that this solution is actually nothing new and is described on StackOverflow.
But for my specific case I think Joes' answer is better. I like it more, because if I override on_model_change I invoke my custom code only if I edit database through Admin webpage; and each programmatic operation over database (like save, update) will work using native code - which is exactly what I want. If I override save method, I will be handling every save operation myself, whether It was initiated by Admin area or programmatically by the server engine.
Solved, thank you!

Related

How do I set a specific value for an attribute of a model every time I save it on Django?

I have a legacy project that saves models with save, bulk_create and other methods within the framework.
What is the best way to set a specific value for an attribute so that every time a record is saved the new value is also saved? This value is constructed based on other attributes of the instance that is being saved.
I pose this question because I'm not sure all ways that save is possible in Django except save and bulk_create and knowing that on bulk_create:
The model’s save() method will not be called, and the pre_save and
post_save signals will not be sent.
https://docs.djangoproject.com/en/1.8/ref/models/querysets/#bulk-create
As far as I know, there are 3 ways to create/update model instances (which are records in database tables):
Using the model instance method save().
Using the queryset methods create(), update(), get_or_create(), update_or_create() and bulk_create().
Using raw SQL or other low-level ways.
If you intend to calculate the value of a field when saving, you could override all of the methods I listed above.
Signals (like pre_create) are not a complete solution because they don't get triggered when bulk_create() is used and so some instance could get saved without the calculated attribute.
There is no django way (that I know) to intercept the third point I mentioned (raw SQL).
You did not elaborate on your use case, but (depending on your table size and change frequency) maybe you could also try:
run a periodical process (maybe using crontab) that updates the calculated field of all model instances.
add a database trigger that calculates the field.
Legacy databases or systems or usually not fun to work with, so maybe you will have to settle for a sub-optimal solution.
You can set default value in your model's field using custom functions. For example you have a Post model that also has a field slug. You want default value for slug field to be auto generated from name field. You can write your model like below:
class Post(models.Model):
def generate_slug(self):
return slugify(self.name)
name = models.CharField()
description = models.TextField()
attachment = models.FileField()
slug = models.CharField(default=generate_slug)
This way when you create a new post, the slug field will be auto generated from the name field.
Another way to do that is to create a layer between your caller and the models(database layer) so you can add your logic there. With this you will narrow the possibilities to just the methods you expose in that layer and have control over what should happen everywhere in terms of database talk.
The best way to deal with this issue is to override the save method().
You can use as well raw sql queries , which can easily solve your problems as well
class Model(model.Model):
field1=models.CharField()
field2=models.CharField()
field3=models.CharField()
def myfunc (self):
pass
#
def save(self, *args, **kwargs):
q = MyModel.objects.select_related('fields1', 'field2', 'filed2').filter(related_field)
super(Model, self).save(*args, **kwargs)

Django: override form validation to get around this error

My specific problem is detailed below, but the main point of my question is this: When I click "Save" on the Django admin form while editing a model, what happens? What is the order of the validation/save process? How can I override these methods to solve my problem below, which is to say updating an intermediate table at the appropriate time as a workaround for the issue I described?
I'm working on a Django application which is facing a PostgreSQL database. The database is relatively large, and I have a lot of many-to-many relationships. The one that I am currently having an issue with is the n:m relationship between People and Sources.
The app is used mainly by non-technical people to add to the database through Django's built-in admin feature. The primary issue that I was having when I started working on the app was severe page slowdown when editing a Person on the admin site. If the person had too many Sources, each drop-down in the TabularInline representation of sources loaded thousands of entries. I found a workaround and overrode the get_formfield_for_foreignkey() method as follows:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
field = super(SourceMaterialInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
# We will overwrite this method to ONLY return relevant materials on this person
materials = SourceMaterial.objects.all()
materials_p = SourceMaterial.people.through.objects.all()
person_ID = get_ID_from_path(request.path)
if not_integer(person_ID):
return field
materials_pid = materials_p.filter(person_id=person_ID)
# The next step is to use these relevant materials to obtain the SourceMaterial objects from the SourceMaterial table
relevant_ids = []
for mat in materials_pid:
relevant_ids += [mat.sourcematerial_id]
relevant_materials = materials.filter(id__in=relevant_ids)
# now we can set the queryset so that only relevant materials are included
field.queryset = relevant_materials
return field
So this accomplishes what I wanted. When you go to edit a Person object through the Admin page, the dropdown menus to select sources only load and display Sources that belong to that Person.
The problem this creates, however, is when I go to add a new Source via the Person change form. I would like to keep this functionality, but it is now broken.
If I go to add a new source, a pop-up window appears. I select the file on my machine, give it a name and click add. At this point it is added to the Sources table, but not to the intermediate table linking it to this Person. When I go to save the Person, the form asks me to correct this error: Select a valid choice. That choice is not one of the available choices.
Here is the pop-up: Add new source material pop-up for clarity
This makes sense, as there is no established link between the Person and the Source yet. So instead I try adding the Person ID as well when adding a source this way, and the error is: Sourcematerial-person relationship with this Sourcematerial and Person already exists. I'm assuming this is because when I add the Source with the Person ID, the intermediate table is populated appropriately, and when I go to save, it tries to add the source again, because the form detects it as being new.
I'm hoping I can override form validation/saving so that I can keep functionality of adding a Source to the database this way. If anybody has any suggestions as to what exactly I could override (I was very confused by the docs) or another work around, that'd be great. Thanks!

Django disable model delete

So every model comes with some commonly used functions such as save and delete.
Delete is often overridden to set a boolean field such as is_active to false, this way data is not lost. But sometimes a model exists that has information that, once created, should always exist and never even be "inactive". I was wondering what the best practice for handling this model's delete method would be?
ideas
make it simply useless:
def delete(self):
return False
but that just seems odd. Is there maybe a Meta option to disable deleting? is there any "nice" way to do this?
Well it depends, you cannot truly restrict deletion, because somebody can always call delete() on queryset or just plain DELETE sql command. If you want to disable delete button in django admin though, you should look here.
delete() on queryset can be restricted with this:
class NoDeleteQuerySet(models.QuerySet):
def delete(self, *args, **kwargs):
pass
class MyModel(models.Model):
objects = NoDeleteQuerySet.as_manager()
...
Django docs - link

Django Save Incomplete Progress on Form

I have a django webapp with multiple users logging in and fill in a form.
Some users may start filling in a form and lack some required data (e.g., a grant #) needed to validate the form (and before we can start working on it). I want them to be able to fill out the form and have an option to save the partial info (so another day they can log back in and complete it) or submit the full info undergoing validation.
Currently I'm using ModelForm for all the forms I use, and the Model has constraints to ensure valid data (e.g., the grant # has to be unique). However, I want them to be able to save this intermediary data without undergoing any validation.
The solution I've thought of seems rather inelegant and un-django-ey: create a "Save Partial Form" button that saves the POST dictionary converts it to a shelf file and create a "SavedPartialForm" model connecting the user to partial forms saved in the shelf. Does this seem sensible? Is there a better way to save the POST dict directly into the db? Or is an add-on module that does this partial-save of a form (which seems to be a fairly common activity with webforms)?
My biggest concern with my method is I want to eventually be able to do this form-autosave automatically (say every 10 minutes) in some ajax/jquery method without actually pressing a button and sending the POST request (e.g., so the user isn't redirected off the page when autosave is triggered). I'm not that familiar with jquery and am wondering if it would be possible to do this.
before Saving:
for field in form.fields:
form.fields[field].required = False
then:
form.save()
The issue is that you have multiple Forms.
Partial. Incomplete. Complete. Ready for this. Ready for that.
Indeed, you have a Form-per-stage of a workflow.
Nothing wrong with this at all.
Figure out where in the workflow you are.
Populate and present the form for the next stage.
Forms can inherit from each other to save repeating validation methods.
Place the following into your form __init__
for field in form.fields:
form.fields[field].required = False
For example:
class MySexyForm(Form):
def __init__(self, *args, **kwargs):
super(MySexyForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
Then call:
form = MySexyForm(...)
form.save()
However you'll need to make sure your clean() method can handle any missing attributes by conditionally checking if they exist in cleaned_data. For example, if another form field validation relies on customer_id but your partial form have not specified one, then customer_id would not be in cleaned_data.
If this is for a model form, you could check if the value was in cleaned_data, and fallback onto instance.field if it was missing, for example;
def clean(self):
inst = self.instance
customer_id_new = self.cleaned_data.get('customer_id', None)
customer_id_old = getattr(self.instance, 'customer_id') if inst else None
customer_id = customer_id_new if customer_id_new else customer_id_old
Remember that the value new value will almost certainly not be in the same format as the old value, for example customer_id could actually be a RelatedField on the model instance but an pk int on the form data. Again, you'll need to handle these type differences within your clean.
This is one area where Django Forms really are lacking sadly.

In the Django admin interface, is there a way to duplicate an item?

Just wondering if there is an easy way to add the functionality to duplicate an existing listing in the admin interface?
In data entry we have run into a situation where a lot of items share generic data with another item, and to save time it would be very nice to quickly duplicate an existing listing and only alter the changed data. Using a better model structure would be one way of reducing the duplication of the data, but there may be situation where the duplicated data needs to be changed on an individual basis in the future.
You can save as by just enabling adding this to your ModelAdmin:
save_as = True
This replaces the "Save and add another" button with a "Save as" button. "Save as" means the object will be saved as a new object (with a new ID), rather than the old object.
There's a better (but not built-in) solution here:
https://github.com/RealGeeks/django-modelclone
From their README:
Django Admin has a save_as feature that adds a new button to your
Change page to save a new instance of that object.
I don't like the way this feature works because you will save an
identical copy of the original object (if you don't get validation
errors) as soon as you click that link, and if you forget to make the
small changes that you wanted in the new object you will end up with a
duplicate of the existing object.
On the other hand, django-modelclone offers an intermediate view, that
basically pre-fills the form for you. So you can modify and then save
a new instance. Or just go away without side effects.
You can also apply this method: https://stackoverflow.com/a/4054256/7995920
In my case, with unique constraint in the 'name' field, this action works, and can be requested from any form:
def duplicate_jorn(modeladmin, request, queryset):
post_url = request.META['HTTP_REFERER']
for object in queryset:
object.id = None
object.name = object.name+'-b'
object.save()
return HttpResponseRedirect(post_url)

Categories