I have multiple databases with different relations and I'm unable to figure out how to properly structure things to avoid problems in Django Admin.
Here is the design I want:
Category
-> 'name' (CharField)
Movie
-> Multiple CharFields
CategoryLink
-> 'category' (ForeignKey -> Category)
-> 'movie' (ForeignKey -> Movie)
-> 'prefix' (ForeignKey -> Prefix) - Blank, Null
-> 'number' (DecimalField)
Prefix
-> 'category' (ForeignKey -> Category)
-> 'prefix' (CharField)
I want to achieve is a structure where:
Each Category can have multiple CategoryLinks to Movies
Movies can exist within any number of Categories any number of times.
CategoryLinks can have a Prefix, but doesn't have to have one.
Prefixes are limited to specific Categories
Editing a Prefix for a specific Category has no effect on a prefix of the same name for a differ Category
Categories can have multiple or zero Prefixes
Prefixes can only be used by a CategoryLink if the Prefix.category == CategoryLink.category
Unrelated Prefixes are hidden from Categories
My main issue is that when I try to limit the choices of Prefixes to only display choices relevant to the current Category I run into a wall. I can do it fine when editing a CategoryLink, but not when adding a new one.
If I try to limit the choices in the Admin Panel with something like this:
#admin.py
# ....
def __init__(self, *args, **kwargs):
super(PrefixForm, self).__init__(*args, **kwargs)
Prefix.objects.filter(category_id=self.instance.category.id)
It works fine while editing, but it throws an error whenever I'm in a form which has the ability to add new entries. Since the new entries doesn't have a defined category.id by virtue (I assume) of not existing yet Django throw an "RelatedObjectDoesNotExist" exception at me.
If I can get it to work while editing an existing Category that would be fine. I don't need to be able to create new CategoryLinks without having a Category already, but when I try I get the same error since I'm using a TabularInline class to include CategoryLinks into Category.
I'm willing to completely restructure everything to get this to work, as you can probably tell I'm new to Django so it's possible I'm thinking about this all wrong.
I asked a similar question last week that I thought I had figured out on my own, but it turned out I simply pushed the problem a step rather than solve it.
Is there a better way to structure my models or is there something I can do within admin.py to properly filter prefixes to categories? Let me know if you want to see all my code, it seemed a bit too much to post here.
I could "solve" this issue by turning CategoryLink.prefix into a CharField, but it would be so much cooler if I could have a separate model for Prefixes.
Here is my full Model.py (minus some irrelevant __unicode__ stuff)
from django.db import models
class Category(models.Model):
class Meta:
verbose_name_plural = "Categories"
name = models.CharField(max_length=255, unique=True)
def category_link_count(self):
return len(CategoryLink.objects.filter(category_id=self.id))
category_link_count.short_description = 'Movies in Category'
class Movie(models.Model):
title = models.CharField(verbose_name='Movie Title', max_length=300)
year = models.DecimalField(verbose_name='Year of Release',
max_digits=4, decimal_places=0)
class CategoryLink(models.Model):
class Meta:
verbose_name = "Category Link"
verbose_name_plural = "Category Links"
category = models.ForeignKey(Category)
movie = models.ForeignKey(Movie)
prefix = models.ForeignKey('Prefix', blank=True, null=True)
number = models.DecimalField(verbose_name='Movie Number', max_digits=2,
blank=True, null=True, decimal_places=0)
class Prefix(models.Model):
category = models.ForeignKey(Category)
prefix = models.CharField(verbose_name='Category Prefix', max_length=255)
class Meta:
verbose_name = "Prefix"
verbose_name_plural = "Prefixes"
ordering = ['-category']
My suggestion is to have defaults for fields that have relations with other models, that way you can create a a Category and have an arbitrary Prefix. However, I think you have structured your models inappropriately.
Category has a ManyToManyField to Prefix which has a FK to Category. You don't need the FK. This is just one, but you can really simplify your organization. I don't know enough about what you want to accomplish.
Related
I have an issue that I am unable to solve. Here is the scenario: there is a retail business which sells cigarettes. This business can have many sites, but each site does not necessarily sell the same types of cigarettes. For this reason, each store owner can browse to a web portal and select the cigarette types that their site sells – these selections will act as columns for a future table. The trouble I am having is writing the data to a database because it is a ManyToMany field. From the data entry perspective, everything appears to be working. The user is able to select what they need and, as a test, I have a HttpResponse message that returns the correct value to the POST method of the view.
The problem that I’m running into is that I don’t know how to save this data in a table because the underlying table is part of a many-to-many relationship. When I try to save the data coming from the form, I get an error:
"Cannot assign "'6565'": "CigCountColumnsMappings.site_id" must be a "Sites" instance."
I have tried many things and I just can’t seem to get this to insert. I think this has something to do with the fact that site_id is a OneToOne/ForeignKey to the Sites model.
My models.py:
class Sites(models.Model):
SITE_STATUS = (
('Open', 'Open'),
('Closed', 'Closed'),
('Maintenance', 'Maintenance')
)
id = models.CharField(max_length=20, verbose_name='Site ID', primary_key=True)
def __str__(self):
return self.id
class CigCountColumns(models.Model):
column = models.CharField(max_length=10, primary_key=True, db_column="column", verbose_name="Column")
def __str__(self):
return str(self.column)
class Meta:
ordering = ["column", ]
verbose_name = "Cigarette Count Column"
verbose_name_plural = "Cigarette Count Columns"
class CigCountColumnsMappings(models.Model):
site_id = models.OneToOneField('home.Sites', on_delete=models.CASCADE, primary_key=True, db_column="site_id", verbose_name="Site ID")
columns = models.ManyToManyField(CigCountColumns, db_column="columns", verbose_name="Cig. Count Column Mappings", blank=True)
def __str__(self):
return str(self.site_id)
class Meta:
ordering = ["site_id", ]
verbose_name = "Cig. Count Column Mapping"
verbose_name_plural = "Cig. Count Column Mappings"
My views.py:
def cigarette_columns(request):
if request.method == "POST":
this_site = request.POST['this_site']
choices = request.POST.getlist('choices')
for choice in choices:
record = CigCountColumnsMappings.objects.create(site_id=this_site, columns=choice)
record.save()
if choices:
return HttpResponse([this_site, choices])
else:
return HttpResponse([this_site, "No data entered"])
The fact that site_id has a reference to the “home.Sites” table is messing me up. I need to insert the values directly into the CigCountColumnsMappings model to map site 6565 to those particular columns but I am unable to do so. I have taken a look at “through” models and read lots of documentation on ManyToMany fields but the solution still eludes me. Many thanks in advance for any help.
First, change site_id to site. This will automatically create a field named site_id that is an integer value for the primary key.
Second, columns is a reference to CigCountColumns, but you assign it as choices which is a list. You need to create a CigCountColumns instance or get one from the database or have the id for a CigCountColumns before you create a CigCountColumnsMappings.
Just as an FYI, I was unable to resolve this issue in a pythonic Django way. I ended up using Django raw SQL queries to hit the table directly (Google: "from django.db import connection") and this resolved it. It's definitely not as "clean" as using the built-in Django methods, but it's not as dirty as completely bypassing Django and using pymysql either.
I searched and could not find a way to do this. Here's my situation:
I have 3 classes:
Publication
Person
AuthorOrder
The last is a through class that allows me to specify the author order for a publication, as this does not normally seem possible to do.
Initially, I made Author a mandatory field (blank=False) for Publication and added a placeholder Person object to add Publications to that don't have a proper author. However, a better solution seems to be to just handle missing authors in Views appropriately. Now, I've changed the field to be optional, but I cannot seem to set the authors to empty via the admin panel. It gives me a "This field is required." error. My guess is that this is because the Person is required of the through class, but setting the Person to null/empty in the admin panel does not set the through object to null/empty.
I found a workaround. One can delete the placeholder Person object. This unsets the through class from the Publications without deleting them via cascade as they are no longer mandatory. However, this is not always a good workaround, so I hope there's a better method.
models.py
There is a lot of code, I reproduce only the minimal necessary:
#Publication
class Publication(models.Model):
authors = models.ManyToManyField(Person, blank=True, related_name="author_of", through='AuthorOrder')
#Person
class Person(models.Model):
# mandatory
firstname = models.CharField(max_length=200)
# AuthorOrder
class AuthorOrder(models.Model):
publication = models.ForeignKey(Publication)
author = models.ForeignKey(Person)
admin.py
# AuthorInline
class AuthorInline(admin.StackedInline):
model = AuthorOrder
extra = 3
# Publication
class PublicationAdmin(admin.ModelAdmin):
list_display = ("title", 'issue', "keywords")
search_fields = ['title', "keywords"]
list_filter = ['issue']
# author list via Inline
inlines = [AuthorInline]
admin.site.register(Publication, PublicationAdmin)
It's simple. To the very right of the "Author order: AuthorOrder object" line, there is a small "Delete" box. Check it and save. It's small and somewhat oddly placed, so easily gets missed.
I've hit a dead end in my database-model design and could use some help.
We are using a Postgres database and django framework to store a variety of papers, books etc.
These are the base classes I'm having trouble with:
class Publisher(models.Model):
name = models.CharField(unique=True)
website = models.URLField(blank=True)
#...
class Editor(models.Model):
firstname = models.CharField()
lastname = models.CharField()
#...
class Book(models.Model):
title = models.CharField(unique=True)
#...
editors = models.ManyToManyField(Editor, blank=True)
publisher = models.ForeignKey(Publisher, blank=True)
#...
The Editor class above is modeled to store the editor(s) as persons of a corresponding book eg. "Ryan Carey" or "Nicholas Rowe".
The Publisher class would hold information like "University of Chicago Press".
The problem hatches should the Editor and Publisher be the same.
For example should the Editor and Publisher both be "University of Chicago Press".
Now, whenever a new Book is saved to the database through the django-admin interface I would like following to happen:
If one or more Editors are given, save it as in the model above.
If no Editors are given but a Publisher is given then make editors point to the same key publisher is pointing to
If none of both are given leave both blank
I was thinking of implementing it somehow like this:
class Book:
#...
def save(self, *args, **kwargs):
if self.editors.count() <= 0 and self.publisher:
self.editors = self.publisher #This is not possible
super(Book, self).save(*args, **kwargs)
The self.editors = self.publisher bit could perhaps be fixed with inheritance, but there are still multiple editors and just one publisher and I dont want publishers and editors to be stored int he same table.
Any thoughts on how to approach this?
With little of re-structuring your models, is possible and will be more flexible than now.
First: don't put publisher and editor to 2 separate models. Make one.
Second: if you can have publisher/editor both person and organization/company (and that will require different model fields), put all common fields into one model and make 2 models that will inherit from that model, containing more specified fields. That will create under the hood one-to-one relation between fields.
Third: create 2 fields in your book model, one ForeignKey named publisher and one ManyToMany named editors. Both relations should point to your base model for publisher/editor.
Sample code:
class PEBase(models.Model): # have no idea how to name it better
some_common_field = models.Field(...)
class PEPerson(PEBase):
first_name = models.CharField()
last_name = models.CharField()
class PEOrganization(PEBase):
name = models.CharField()
website = models.URLField()
class Book(models.Model):
title = models.CharField(unique=True)
#...
editors = models.ManyToManyField(PEBase, blank=True, related_name="books_edited")
publisher = models.ForeignKey(PEBase, blank=True, related_name="books_published")
def save(self, *args, **kwargs):
super(Book, self).save(*args, **kwargs)
if self.editors.count() <= 0 and self.publisher:
self.editors.add(self.publisher) #This must go after save - ID of book must be already set.
Using the django-rest-framework is it possible to retrieve content from a related field. So for example I want to create a genre list which contains all projects within it. This is what I have but I keep on getting the error:
'Genre' object has no attribute 'project_set'
models.py
class Genre(models.Model):
name = models.CharField(max_length=100, db_index=True)
class Project(models.Model):
title = models.CharField(max_length=100, unique=True)
genres = models.ManyToManyField(Genre, related_name='genres')
serializers.py
class GenreSerializer(serializers.ModelSerializer):
project_set = serializers.ManyRelatedField()
class Meta:
model = Genre
fields = ('name', 'project_set')
The related name you're using on the Project class is badly named. That related name is how you access the set of projects related to a given genre instance. So you should be using something like related_name='projects'. (As it is you've got it the wrong way around.)
Then make sure that your serializer class matches up with the related name you're using, so in both places project_set should then instead be projects.
(Alternatively you could just remove the related_name='genres' entirely and everything will work as you were expecting, as the default related_name will be 'project_set'.)
I have a Django application with following class:
class Opinion(models.Model):
id = models.AutoField(primary_key=True)
contents = models.CharField(max_length=256)
source = models.CharField(max_length=256)
proArguments = models.ManyToManyField('self', verbose_name="Pro arguments", related_name='proargs', null='true', blank='true')
contraArguments = models.ManyToManyField('self', verbose_name="Contra arguments", related_name='contraarg', null='true', blank='true')
def __unicode__(self):
return self.contents
When I try to create a new instance of this class in the admin, the newly created opinion has one proArgument and one contraArgument, even though I didn't enter them.
What can I do in order for proargs and contraarg to be empty, when I don't enter them?
The multiple select widget for both many to many fields contains all possible Opinions. In your screenshot, there are no Opinions selected for these fields. They are not selected until you click on one or more opinions and save.
You might find the filter_horizontal and filter_vertical model admin options helpful. They make it clearer which objects are selected.