Django's Admin - Many-to-Many field confusion - python

I'm teaching myself Django and creating a personal portfolio site. I've setup a model called FolioItem which makes use of a Many-To-Many relation to another model called FolioImage. The idea being each FolioItem can have numerous FolioImages.
class FolioImage(models.Model):
image = models.FileField(upload_to='portfolio_images')
def __unicode__(self):
return self.image.name
class FolioItem(models.Model):
title = models.CharField(max_length=50)
thumbnail = models.FileField(upload_to='portfolio_images')
images = models.ManyToManyField(FolioImage)
def __unicode__(self):
return self.title
In Django's admin interface I can create new FolioItem's but the images field (the many-to-many relating to a FolioImage) is showing all existing FolioImage's added previously to other FolioItems.
Really this should be blank as it is a new item and doesn't have any images associated yet. Is there any way to force this selector to be blank when creating a new item?
Also, it seems when I attempt to edit an existing entry I can't add or delete FolioImage associations (the small green + icon is not present).
Any advice would be great.

I think you are using the wrong relationship here. If you only want FolioImages to belong to a single FolioItem, then you should be using a ForeignKey from FolioImage to FolioItem. You can then configure the admin interface to show the related images for an item as inline formsets, which will allow you to add an edit only the images for that item.

Is there any way to force this
selector to be blank when creating a
new item?
I think you are slightly confused about the interface to the default select multiple widget. It is showing you all the existing images, making them available to you as choices, but there is no relationship on a brand new item. You have to actually click on an item, or do the shift (or control)-click trick to select multiple items, then save your folio item in order to make the relationship.
Also, it seems when I attempt to edit
an existing entry I can't add or
delete FolioImage associations (the
small green + icon is not present).
Again, I think you just need to click or ctrl-click to select/deselect the images you want to associate with your item.
You may also be interested in using Django's cool horizontal or vertical filter instead of the default select multiple interface. You can also use the inline interface. More details are here and here. I think the filter interface is much more intuitive.

Related

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.

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!

Setting default dropdown selection for model in Django admin interface

Sorry if the title is hard to follow, I can't think of a concise way to word this.
In my Django project, I have five models, ComputingObject, Laptop, Desktop, HardDrive, and SanitizationMethod. They're linked as such:
class ComputingObject(models.Model):
#various other fields...
sanitize_method = models.ForeignKey(SanitizationMethod)
class Laptop(ComputingObject):
#laptop-related fields
class SanitizationMethod(models.Model):
description = models.CharField(max_length=100)
Desktop and HardDrive also extend ComputingObject.
So in the admin interface, when creating a new instance of any of the three ComputingObject children, sanitize_method is shown as a drop-down (select) field. In my project, there are a small handful of SanitizationMethods in the database. What I want is to have that drop-down menu default to a specific (different) choice for each of the three ComputingObject children. For instance, Desktop might default to "Remove hard drives", HardDrive would be "Degauss and destroy", etc. Is there a way to do this with django?
On the surface it seems as easy as overriding the sanitize_method field for each of the three children, and putting defualt= x in each, but django disallows overriding parent fields, so this approach won't work.
I'm stumped. Does anyone know how to do this (or if it's even possible)?
You probably want to override formfield_for_foreignkey in your child models ModelAdmins, cf https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey

Create new foreign key in form

I'm using django autocomplete_light and have two models connected via one-to-many relationship. Model A has a ForeignKey field TAG to model B. It all works, but I can only select the existing Tag, it is not possible to automatically add new Tag, even though it is possible to freely type in the box.
How can I "intercept" validation and create the suitable database entry for tag in time?
You could use an add another popup like in django admin.
Here's a live example using this code. The design is not very very good but it demonstrates the point.

How to correlate gtk.ListStore items with my own models

I have a list of Project objects, that I display in a GtkTreeView. I am trying to open a dialog with a Project's details when the user double-clicks on the item's row in the TreeView.
Right now I get the selected value from the TreeView (which is the name of the Project) via get_selection(), and search for that Project by name in my own list to corelate the selection with my own model.
However, this doesn't feel quite right (plus, it assumes that a Project's name is unique), and I was wondering if there is a more elegant way of doing it.
Not with the default models. You could try using Py-gtktree models written specifically to use the same objects in backend and presentation.
Its documentation outlines an alternative way to make this work with standard models (i.e. without using Py-gtktree at all), by the way, but I wouldn't call it elegant.
What I ended up doing was extending gtk.ListStore and use my custom list. I also hijacked the append() method so that not only it will append a [str, str, etc] into the ListStore, but also the actual model inside a custom list property of the class that extends ListStore.
Then, when the user double clicks the row, I fetch the requested model by the row's index in the ListStore, which corresponds to the model's index in the custom list.

Categories