Django: override form validation to get around this error - python

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!

Related

How can I add a changeable "subfield" to a ModelField in django?

I'm trying to create a kind of "subfield" for a CharField in django, but I'm not sure (a) if it is possible at all and (b) how to succeed if it is indeed possible.
Let's say I want a model for Tools. They would have a, e.g., a field for long_name, short_name, maybe a ForeignKey for realizing different departments. One of these tools I'd like to be a Link, the said "subfield" being a URLField with the href to the webpage.
Now, I can create multiple link entries with the associated URL, but I'd rather have only one tool called "Link" with the changing URL attached. Is this a case for ForeignKey as well? Does it make sense to have a model with only one field (well, two if you count the pkid) in it?
Or am I on a completely lost path here?
If I've understood you correctly, you want to have a number of links that can be attached to a Tool model, so instead of just having a single URLField you would have a Many-to-One relation with a Link model:
class ToolLink(models.Model):
url = models.URLField(...
class Tool(models.Model):
links = models.ForeignKey(ToolLink, ...
The problem is that you only want one particular tool to be able to hold links. Your options are to create a 'Tool' base model that then has multiple different types of tool, like 'StandardTool', 'LinkTool', etc. or to setup some logic that monitors whether the Tool has links or not (or if another tool already has links) and whether creating links is acceptable.

Edit FileField file contents from Django Admin

I have a situation where I have python scripts that are stored as files that need to be modified from the Admin panel. Currently some of my models can have scripts attached to them:
class Action(PolymorphicModel):
title = models.CharField(max_length=100)
text = BBCodeField('text', blank=True)
action_script = models.FileField(upload_to='action_scripts')
In my Admin panel I can upload a file but I have been asked to make this file editable in-place if the user has the right permissions. I've searched all over for an idiomatic way to do this, and so far I've come up with the following ideas:
Override the FileField widget to include an text edit box and save button. This seems very brute forced
Replace models.FileField with subclass of models.TextField, implement read from file, save to file, but then I lose the upload widget and data still gets stored in DB. This seems dirty because in the database all I'm storing is a filename. FileField feels like the right field type here.
Write a custom ModelForm that includes forms.TextField. Handle save to file using the save method. I'm not sure how to accomplish this because I would have to generate a filename on the fly and I don't have a primary key yet. Maybe some sort of UUID?
I'm leaning against door number 3 right now, but I've only been using Django for a week, so I'm not sure this is the right way to go, or even how to do it. Also I would need to have a custom ModelForm for every model that inherits from Action, which violates DRY.
Further complications:
Not all Actions need scripts. If the textbox is empty, the FileField can just become Null.
Action is subclassed into a handful of derived classes. The editing functionality should extend to each model's Admin panel in a scalable way, like the other fields.
This question seems generic enough that its solution would be useful for those wishing to edit HTML or CSS from the admin, etc, the same as WordPress does. Searching along those lines unfortunately brings up results to do with skinning the admin, which I'm not after. In my situation I'm trying to avoid storing python code in the database for security reasons. The Admin interface is not going to be exposed to the end users so in theory it should be safe, and I get the benefit of caching, compilation and locality (less hits to the DB).
Many thanks.

Python django admin: How can I show only items belonging to specific model in an admin page?

I'm learning django for building my website.
I learned how to create models, views, but now I have some question relative to admin interface.
So I have tis code:
from django.db import models
class AudioBook(models.Model):
titolo=models.CharField(max_length=250)
capitoli=models.ManyToManyField('Chapters')
class Chapters(models.Model):
capitolo=models.CharField(max_length=250)
Now, when I add a new audiobook I can see chapters previously added to others audiobooks. This is bad. I want only the chapters taht belong to this audiobook, so, when I add a new one, the list must be empty.
I tried some things: adding limit_choices_to = models.Q(audiobook__titolo=titolo), but it doesn't work.
In command line I can retrieve these info by adding filters on Chapters object, but I can reproduce this situation in admin interface.
Any idea? I searched on google but I didn't find anything that helps me.
Germano
For ManyToMany relationships, Django admin interface allows you to see the entire list of choices you have and then you select whichever you want to add to your AudioBook object.
It does not mean all of them belong to that audio book. The selected ones (the ones you added to you audio books) will have normally a grey background
If that interface bothers you that much, I suggest that you add AudioBook foreign key in Chapters instead of your MAnyToMany relationship:
class Chapters(models.Model):
capitolo=models.CharField(max_length=250)
audio_book = models.ForeignKey(AudioBook,on_delete=models.CASCADE)
It's more relevent in your case. You still can access the audio book's chapters as easily.

Option to add extra choices in django form

I am trying to create a model via a form that has multiple other models related to it. Say I have a model Publisher, then another model Article with a foreign key to Publisher. When creating a Publisher via a form, I want to create An article at the same time. I know how to do this via formsets. However, I don't know how to add a button that says add extra article at the same view, without having to be redirected to a new page and losing the old data since the form was not saved. What I want is when someone clicks add new article, for a new form for article to appear and for the user to add a new Article. Is this possible to be done in the same view in django, if so can someone give me and idea how to approach this?
I would show code or my attempts, but I am not sure how to even approach it.
This can only be done using JavaScript. The hard part is to have the management form sync up with the number of rows.
But there's two alternatives:
Semi-javascript (Mezzanine's approach): Generate a ton of rows in the formset and only show one empty. Upon the click of the "add another row" button, unhide the next one. This makes it easier to handle the management form as the unfilled extra's don't need any work.
No fix needed: Add as many rows as is humanly sane. In general, people don't need 40 rows, they get bored with filling out the form or worry that all that work is lost when the browser crashes.
Hope this helps you along. Good luck!

Django import-export importing data with object relationships

I've been playing with django import-export because it seems to be the obvious choice for anything import/export related in django and it's fantastic. Trouble is I can't find much in the way of docs on the import side of things and I'd like to be able to use it to import data through and cope with table relationships.
For example I've got a system for a cycling event, and an event has a table of various places managed by a user. I'd like that user to be able to import a file of details for people who will fill those places.
The places the user has have a FK to another table for cyclists in this event and the uploaded file would contain cyclists details. However the cyclist objects may not exist yet.
I've been working with django-import-export from an admin perspective, targeting specific model objects by ID from a CSV, but is it possible to cope with object relationships?
My thought was that from the view where a user can see their places in an event and add a cyclist to each one individually or upload a CSV file they could also download a CSV file. This would contain the ID for each place and a column for each piece of cyclist information I want to be supplied.
I know from the export side of this I was able to specify place__cyclist__name to get the name of a cyclist associated with a place. Is the same possible for importing data?
It's answered here,
Adding foreignKey widget to django-import-export
And here
import m2m relation in django-import-export
For more details: django-import-export Widgets

Categories