How do I reference Django Model from another model - django

Im looking to create a view in the admin panel for a test program which logs Books, publishers and authors (as on djangoproject.com)
I have the following two models defined.
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
def __unicode__(self):
return u'%s %s' % (self.first_name, self.last_name)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
def __unicode__(self):
return self.title
What I want to do, is change the Book model to reference the first_name of any authors and show this using admin.AdminModels.
#Here is the admin model I've created.
class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'publisher', 'publication_date') # Author would in here
list_filter = ('publication_date',)
date_hierarchy = 'publication_date'
ordering = ('-publication_date',)
fields = ('title', 'authors', 'publisher', 'publication_date')
filter_horizontal = ('authors',)
raw_id_fields = ('publisher',)
As I understand it, you cannot have two ForeignKeys in the same model. Can anyone give me an example of how to do this?
I've tried loads of different things and its been driving me mad all day. Im pretty new to Python/Django.
Just to be clear - I'd simply like the Author(s) First/Last name to appear alongside the book title and publisher name.
Thanks

You can have more than one Foreign Key on a model.
If you would put a Foreign-Key field's name in list_display you will always just see the __unicode__ representation of the associated model.
But you can add a function like this to your BookAdmin:
def first_names(self, obj):
return ','.join(a.first_name for a in obj.authors.all())
get_sites.short_description = 'First Names'
Then add 'first_names' to list_display.

Related

Modifying django object model for better admin CRUD management

I am writing a blog app using django 1.10
This is a snippet of my object model:
model.py
class Attachment(models.Model):
title = models.CharField(max_length=50)
image = models.ImageField(upload_to='attachments')
class FileAttachments(models.Model):
title = models.CharField(max_length=50)
attachments = models.ManyToManyField(Attachment)
class Post(models.Model):
title = models.CharField(max_length=200)
text = models.CharField(max_length=2000)
file_attachments = models.ForeignKey(FileAttachments, blank=True, null=True)
slug = models.SlugField(max_length=40, default='', unique=True)
author = models.ForeignKey(User, default=1)
pub_date = models.DateTimeField(blank=True, null=True)
def get_absolute_url(self):
return "/blog/%s/%s/%s/%s/" % (self.pub_date.year, self.pub_date.month, self.pub_date.day, self.slug)
def __unicode__(self):
return self.title
class Meta:
verbose_name = "Blog Post"
verbose_name_plural = "Blog Posts"
ordering = ["-create_date"]
permissions = (
( "create", "Create Post" ),
( "modify", "Modify Post" ),
( "delete", "Delete Post" ),
)
(simplified) admin.py:
class PostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
exclude = ('author',)
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
# Register your models here.
admin.site.register(Post, PostAdmin)
when I try to access the Post object via the admin page - in the list view, I only see 'Post object' - whereas I want to see the title of the post (and possibly, a few other attributes of the Post object) - how do I modify the admin view to achieve this?
For your first problem - you need to define list_display in your PostAdmin, like that:
class PostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
exclude = ('author',)
list_display = ('title', 'pub_date')
As for your second problem - please stick to the 'One post, one problem' rule; hint - ForeignKey means only one FileAttachment can be related to your Post.
Change __unicode__ for __str__ in your Post class and print whatever you want. For example: return "Blog %s published on %s" %(self.title, self.pub_date).
Your Post model only includes one attachment through a foreign key. That way it will be impossible to upload more than one file. In other words, you have to change your models, for example including a key in FileAttachments relating to a Post and removing the key from Post model.
Hope this helps.

Try to join a OneToOne relationship in Django

I need some help doing a join using Django, which seems like it should be easy. I have looked at the documentation but it seems like it won't join for some reason.
I am trying to get in my view, the model.Photo and model.PhotoExtended with both joined and then displayed in the view. Currently I am just trying to get the model.Photo displayed but with a join which finds the request.user and filters it based on that.
They are in different apps.
models.py for model.Photo
class Photo(ImageModel):
title = models.CharField(_('title'),
max_length=60,
unique=True)
slug = models.SlugField(_('slug'),
unique=True,
help_text=_('A "slug" is a unique URL-friendly title for an object.'))
models.py for model.PhotoExtended
class PhotoExtended(models.Model):
Photo = models.OneToOneField(Photo, related_name='extended', help_text='Photo required', null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, help_text='User that uploaded the photo')
views.py
class PhotoExtendedUserView(ListView):
template_name = 'photo_user_list.html'
def get_queryset(self):
user = get_object_or_404(User, username=self.request.user)
return Photo.objects.filter(photoextended__user=user)
You set the related_name on Photo (which shouldn't be capitalized by the way) to extended so you need to filter like so:
class PhotoExtendedUserView(ListView):
template_name = 'photo_user_list.html'
def get_queryset(self):
user = get_object_or_404(User, username=self.request.user)
# 'extended' vs. 'photoextended'
return Photo.objects.filter(extended__user=user)

Show inline model field in list_display

I have two models:
class Person(models.Model):
person_name = models.CharField(max_length=255)
def __unicode__(self):
return self.person_name
and
class Book(models.Model):
link = models.ForeignKey(Person)
book_name = models.CharField(max_length=255)
book_year = models.CharField(max_length=255)
book_email = models.CharField(max_length=255)
def __unicode__(self):
return self.name
admin.py
class PersonAdmin(admin.ModelAdmin):
inlines = ('BookInline',)
list_display = ('person_name', ...)
class BookInline(admin.TabularInline):
model = Book
extra = 1
max_num = 1
In PersonAdmin list_display, how can i show the inline model Book fields (title, name, email).
so when i access Person list of entries in django admin, i see:
person name book name book year book email
The list_display for the PersonAdmin is meant to display each person once. It doesn't really make sense to include attributes from the book model, because then you would have to include the person multiple times if they have more than one book.
Wouldn't it be better to include the person's name on the book's list_display?
class BookAdmin(admin.ModelAdmin):
inlines = ('BookInline',)
list_display = ('person_name', 'book_name', 'book_email', 'book_year')
def person_name(self, obj):
return obj.link.person_name
admin.site.register(Book, BookAdmin)
According to the doc, you can use a callable to display your fields.
Edit (2021/03/21): the link to the 1.9 doc is not working anymore, but the source is still available. I add the link to the dev version too.
class BookInline(admin.StackedInline):
model = Book
extra = 1
max_num = 1
StackedInline will solve your problem

ManyToManyField Serializer throws "This field must be unique" error

I am trying to create a Many-To-Many relationship between two models- Author and Book. My use-case is that I should be able to add a new book to the database with an author that already exists in the database.
models.py
class Author(models.Model):
author_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('author_id',)
class Book(models.Model):
title = models.CharField(max_length=50, primary_key=True)
authors = models.ManyToManyField(Author)
def __unicode__(self):
return self.title
class Meta:
ordering = ('title',)
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('author_id', 'name')
class BookSerializer(serializers.ModelSerializer):
authors = AuthorSerializer(many=True)
class Meta:
model = Book
fields = ('title', 'authors')
def create(self, validated_data):
book = Book.objects.create(name=validated_data['title'])
for item in validated_data['authors']:
author = Author.objects.get(author_id=item['author_id'])
book.authors.add(author)
return book
Let's say my Author table already has an Author:
1, George RR Martin
Now if I want to add a new book with an existing author, this is the request I send using httpie:
http -j POST http://localhost/books title="The Winds of Winter" authors:='[{"author_id":"1"}]'
and when I do, I get this error:
Output Error
{
"authors": [
{
"author_id": [
"This field must be unique."
]
}
]
}
It seems like the AuthorSerializer is being called which checks the provided author_id against the ones in the database already and throws this error.
Any help on this would be appreciated.
Is there a specific reason you have to use a custom PK field?
Django automatically creates primary key fields for you. If you simply delete that field from your model and your serializer (and create/run a migration on your database), you won't have to specify the pk in your POST call from your frontend, and Django will create an AutoField that auto-increments your model's id:
class Author(models.Model):
# Remove this line and run makemigrations.
# author_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('author_id',)
If not, consider using an models.AutoField rather than models.CharField for your primary key field, and again, don't include this in your POST call.
Note, that if you already have a big database created, you might have to do some intricate work in your migration, a la this answer:

Django ModelForm - form display values

I have a model:
class SchedulerProfile(models.Model):
user = models.ForeignKey(User, unique=True)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __unicode__(self):
return u'%s' % (self.user)
I also have a model that has a M2M relationship with the Scheduler:
class OfficialProfile(models.Model):
user = models.ForeignKey(User, unique=True)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
schedulers = models.ManyToManyField(SchedulerProfile, verbose_name="Your schedulers", related_name="off_schedulers")
When I create a ModelForm from the OfficialProfile, the form renders the contents of the scheduler multiselect as the username. I'd like to get it to display user.first_name user.last_name in the field instead of the user. I know I can change the unicode return value to accomplish this, but I'd rather not do that for reasons. In the __init__ of the ModelForm, you should be able to do something like:
self.fields['schedulers'].queryset = SchedulerProfile.objects.values('user__first_name', 'user__last_name')
But that doesn't produce the desired results. This seems like a really basic question, but searching for the answer hasn't revealed much.
Subclass ModelMultipleChoiceField and override label_from_instance.
from django.forms import ModelMultipleChoiceField
class SchedulerProfileChoiceField(ModelMultipleChoiceField):
def label_from_instance(self, obj):
return "%s %s" % (obj.first_name, obj.last_name)
Then use your multiple choice field in the model form.
class OfficialProfileForm(forms.ModelForm):
schedulers = SchedulerProfileChoiceField(queryset=SchedulerProfile.objects.all())