Just starting out with Django and I have a basic model for creating blog posts. Basic idea is I want each post to have multiple tags and hopefully later use those for filtering my posts.
class Tag(models.Model):
slug = models.SlugField(max_length=50, unique=True)
def __str__(self):
return self.slug
class Meta:
verbose_name = "Tag"
verbose_name_plural = "Tags"
class Post(models.Model):
post = models.TextField()
pub_date = models.DateTimeField('date published', auto_now=True)
title = models.CharField(max_length=100)
slug = models.SlugField(max_length=100, unique=True)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
class Meta:
verbose_name = "Blog Entry"
verbose_name_plural = "Blog Entries"
ordering = ["-pub_date"]
on my admin side I'm wondering the best way be able to add posts. I've been reading a bunch of threads where people suggest to use an inline style for manytomany relationships, but don't see the difference between that and just editing the object.
So why go with this
class TagInline(admin.TabularInline):
model = Post.tags.through
extra = 1
class newsAdmin(admin.ModelAdmin):
list_display = ('title', 'pub_date')
prepopulated_fields = {"slug": ("title",)}
inlines = [TagInline]
exclude = ('tags',)
admin.site.register(Post, newsAdmin)
admin.site.register(Tag)
over this?
class newsAdmin(admin.ModelAdmin):
list_display = ('title', 'pub_date')
prepopulated_fields = {"slug": ("title",)}
admin.site.register(Post, newsAdmin)
admin.site.register(Tag)
An inline has (almost?) no benefit over a regular field in the case of a default through model for a ManyToManyField. But consider the following (simplified) model:
STATUS = (
('pending', 'Pending'),
('accepted', 'Accepted'),
('rejected', 'Rejected'),
)
class Comment(models.Model):
post = models.ForeignKey(Post)
comment = models.TextField()
status = models.CharField(max_length=20, choices=STATUS)
The primary objective is no longer to connect the right comment to the right post. However, it would be very convenient to moderate all comments on a post on the edit page of the post itself, instead of moderating each comment on a separate page. An inline allows you to change the status of each comment on a single page.
Basically, if you just want to select which items to relate to your primary object, an inline isn't needed. If you need to frequently edit the objects related to your primary object, an inline provides a huge convenience.
Related
I have the following models:
class Tag(TimeStampModel):
name = models.CharField(unique=True, max_length=100)
slug = models.SlugField(max_length=100, unique=True, blank=True)
featured = models.BooleanField(default=False, blank=True)
class Deal(VoteModel, models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='deals',
on_delete=models.CASCADE)
title = models.CharField(max_length=1024, blank=False, null=False)
slug = models.SlugField(max_length=1024, unique=True, blank=True)
description = models.TextField(blank=False, null=False)
poster = models.ImageField(blank=True)
tags = models.ManyToManyField(
Tag, blank=True)
And the following serializers:
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug', 'featured', 'created_at']
class DealSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, read_only=True)
tags = TagSerializer(many=True, read_only=True)
tags_ids = serializers.PrimaryKeyRelatedField(many=True, write_only=True, queryset=Tag.objects.all())
class Meta:
model = Deal
fields = '__all__'
Views
class DealList(viewsets.ModelViewSet, VoteMixin):
serializer_class = DealSerializer
permission_classes = [IsOwnerOrAdminOrReadOnly]
def get_queryset(self):
return Deal.objects.all()
def perform_create(self, serializer):
serializer.save(user=self.request.user)
I am able to get the data and also post it, but because of the many-to-many field (tags), I seem to have some issues as a Deal may have tags that only exist (created beforehand, and cannot be created through a post request to the Deal).
I send data as the following:
{
title: 'some title',
all_other_fields: 'some data',
tags_ids: [2, 4]
}
The tags are sent as an array of tag ids, but I get an error as the following:
"Incorrect type. Expected pk value, received str."
I only added the tags_ids so I could perform write operations on it as I couldn't figure out a way to use the field tags as both a read and write field that would return an object on read, and accept an id on write.
I have read through many posts here on Stackoverflow, but nothing that would work for me yet. Any help would be very appreciated. Thank you.
Try changing your serializer like this
class DealSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, read_only=True)
tags = serializers.PrimaryKeyRelatedField(many=True, queryset=Tag.objects.all())
class Meta:
model = Deal
fields = '__all__'
Try doing it this way.
class DealSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
tags = TagSerializer(read_only=True)
class Meta:
model = Deal
fields = ('user', 'title', 'slug', 'description', 'poster', 'tags')
I have following model in django:
class ProjectScheme(models.Model):
name = models.CharField(max_length=250, blank=False,null=False)
parent_scheme_id = models.ForeignKey(ProjectSchemeMaster, on_delete = models.CASCADE)
rule = models.TextField(blank=True)
created_on = models.DateTimeField(auto_now=False, auto_now_add=True)
created_by = models.IntegerField(blank=True,null=True)
updated_on = models.DateTimeField(auto_now=True, auto_now_add=False)
updated_by = models.IntegerField(blank=True,null=True)
def __str__(self):
return str(self.name)
And in admin.py
# Register your models here.
class ProjectSchemeAdmin(admin.ModelAdmin):
exclude = ('id',)
class Meta:
model = ProjectScheme
admin.site.register(ProjectScheme, ProjectSchemeAdmin)
But date fields: updated_on, created_on don't show up on admin form.
I also tried without ProjectSchemeAdmin class, and also:
class ProjectSchemeAdmin(admin.ModelAdmin):
pass
Also,
# list_display = [field.name for field in ProjectScheme._meta.fields if field.name != "id"]
list_display = ["id", "name","parent_scheme_id","rule","created_on", "created_by","updated_on","updated_by"]
But the same.
I need to get all the fields in admin form.
You need to add readonly_fields = ('created_on','updated_on') to display the date field.
# Register your models here.
class ProjectSchemeAdmin(admin.ModelAdmin):
list_display = ["name","parent_scheme_id","rule","created_on", "created_by","updated_on","updated_by"]
readonly_fields = ('created_on','updated_on')
class Meta:
model = ProjectScheme
admin.site.register(ProjectScheme, ProjectSchemeAdmin)
As mentioned in the docs:
As currently implemented, setting auto_now or auto_now_add to True will cause the field to have editable=False and blank=True set.
The value auto_now_add is True. So no matter what date value you enter via admin UI, it will ultimately be overriden, which means there is no reason to display the field. In this case, as you need to display the field anyway, adding the readonly_fields does the trick.
Hope this helps!
I have a models like
class Section(models.Model):
section_title = models.CharField(max_length=200)
section_tiles = models.ManyToManyField(Tile, blank=True)
on_menu = models.BooleanField(default=False)
section_is_active = models.BooleanField(default=True)
class Meta:
verbose_name = "Section"
verbose_name_plural = "Sections"
def __unicode__(self):
return self.section_title
class Page(models.Model):
page_title = models.CharField(max_length=200)
page_sections = models.ManyToManyField(Section, blank=True)
on_menu = models.BooleanField(default=False)
page_is_active = models.BooleanField(default=True)
class Meta:
verbose_name = "Page"
verbose_name_plural = "Pages"
def __unicode__(self):
return self.page_title
And at the admin part I have code such as:
class SectionTabularInline(admin.TabularInline):
model = Page.page_sections.through
class PageAdmin(admin.ModelAdmin):
inlines = [SectionTabularInline,]
list_display =[
'page_title',
'on_menu',
]
list_filter =[
'on_menu',
]
search_fields = [
'page_title',
]
ordering = ['page_title']
admin.site.register(Page, PageAdmin)
When I make changes in inline section of the page, Its giving an error "Please correct the errors below." without any additional information. If I remove sections from page and save it, then I can reassign them from scratch. Is there any way to change them without removing them first?
From: Save M2M "Through" Inlines in Django Admin
I found my problem: if you want many to many relationship you need to keep your original model's primary key(id).
I have designed following models for my blog
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField(default='')
created_at = models.DateTimeField('created date', auto_now_add=True, auto_now=False)
updated_at = models.DateTimeField('updated date', auto_now_add=False, auto_now=True)
author = models.ForeignKey('Author', default='admin')
def __str__(self):
return self.title
class Author(models.Model):
name = models.CharField(max_length=150)
email = models.EmailField(blank=True)
bio = models.TextField()
def __str__(self):
return self.name
class Category(models.Model):
cat_name = models.CharField(max_length=200)
post = models.ManyToManyField('Post')
def __str__(self):
return self.cat_name
class Tag(models.Model):
tag_name = models.CharField(max_length=200)
post = models.ManyToManyField('Post')
def __str__(self):
return self.tag_name
and I am trying to register this model under django admin in such a way that. I can edit the Category, Tags and Authors from the Post page. but I am having hard time to accomplish this talk, I have written this code in admin.py file
from django.contrib import admin
from .models import Post, Author, Tag, Category
class AuthorInline(admin.TabularInline):
model= Author
class TagInline(admin.StackedInline):
model= Tag
class CategoryInline(admin.StackedInline):
model = Category
#admin.register(Post) #another method of registration admin.site.register(Post, PostAdmin)
class PostAdmin(admin.ModelAdmin):
#Show the following fields in this order
fields = ['body', 'title']
#show the following filelds for nice formattng
list_display = ['title', 'author', 'created_at']
#display based on the date hirerarchy
date_hierachy = 'created_at'
#embed the following child models in this parent models
inlines = [AuthorInline, TagInline, CategoryInline,]
#to exclude fields
exclude = ('author',)
When I run my server I got the errors like
ERRORS:
<class 'blogs.admin.AuthorInline'>: (admin.E202) 'blogs.Author' has no ForeignKey to 'blogs.Post'.
<class 'blogs.admin.CategoryInline'>: (admin.E202) 'blogs.Category' has no ForeignKey to 'blogs.Post'.
<class 'blogs.admin.TagInline'>: (admin.E202) 'blogs.Tag' has no ForeignKey to 'blogs.Post'.
when investigating the error, we cannot have StackedInline class if the models doesn't have foreign key, but How can I put the Tags, Category and Author rendered formm under the Post page in django admin,
For using AuthorInline, you ned a foreignkey field in you Author model
ex:
class Author(models.Model):
post = models.ForeignKey('Post')
This means one post may have multiple authors.
But here in your situation you have the correct model and fileds which have one author for one post, so you can remove AuthorInline.
And incase of Tag and Category, you are using many-to-many field, It will be good if you go through this documentation https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-models
You have to rewrite the CategoryInline and TagInline;
class TagInline(admin.StackedInline):
model= Tag.post.through
class CategoryInline(admin.StackedInline):
model = Category.post.through
This isn't what inlines are for, and you don't want them here.
Inlines are for the reverse relation: given an author, edit their details and enter all their books on the same page. Your foreign keys and many-to-many fields are best shown as simple widgets, which is what Django does by default; the author and category will be displayed as a dropdown allowing you to choose an item, and the tags will be displayed as a multi-select box.
You might also choose to register Book as an inline on the Author admin; that's up to you.
Finally I made, what I wanted, the main gist is to make the category, author and tags choosable from the post page, so to do that, we need to add all the fields in the post model, which is the modified model
from django.db import models
from django.utils import timezone
class Author(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(blank=True)
bio = models.TextField()
class Tag(models.Model):
tag_name = models.CharField(max_length=50)
class Category(models.Model):
cat_name = models.CharField(max_length=50)
class Post(models.Model):
'''post can have many categories
and categories can have many post
author can have many post but post
can have single author
post can have many tags, and tags
can have many posts'''
title = models.CharField('post title', max_length=200)
body = models.TextField(default='', null=True)
created_at = models.DateTimeField(auto_now_add=True, auto_now=False)
updated_at = models.DateTimeField(auto_now_add=False, auto_now=True)
author = models.ForeignKey(Author, verbose_name = "List of Author") #many to one relationship
def __str__(self):
return self.title
#Generally many to many fields should into that model which is going to be edited.
tags = models.ManyToManyField(Tag)
categories = models.ManyToManyField(Category)
class Meta:
ordering = ['-created_at']
verbose_name_plural = "Posteeees"
# def post_status(self):
# return timezone.now() - self.updated_at <= 1
#Recursive realation, we can define the foreignkey itself to the model and this is called rrecursive realation
#
Django admin shows an empty field instead of "ajax select widget".
Tried on the other project - in models with two m2m relations to completely equal models with different names. Works like a charm on one field, shows empty place on other. Any help or links?
models.py
class Company(models.Model):
"""Companies - customers """
title = models.CharField('Nosaukums', blank=False, max_length=200)
is_costumer = models.BooleanField('Pasūtītājs', default=False)
is_subcontractor = models.BooleanField('Apakšuzņēmējs', default=False)
class Meta:
verbose_name = 'Uzņēmums'
verbose_name_plural = 'Uzņēmumi'
def __unicode__(self):
return self.title
class Project(models.Model):
"""Projects"""
number = models.IntegerField('Īsais numurs', blank=False, null=False)
title = models.CharField('Nosaukums', blank=False, max_length=250)
customers = models.ManyToManyField(Company, verbose_name='Pasūtītāji', blank=True, null=True)
is_active = models.BooleanField('Aktīvs', default=True)
notes = models.TextField('Piezīmes', blank=True)
class Meta:
verbose_name = 'Projekts'
verbose_name_plural = 'Projekti'
def costumer_list(self):
list = "pasūtītāji"
return list
def __unicode__(self):
return self.title
Based on the model you posted above and the image of the admin file, your filter_hoizontal variable should be filter_horizontal = ('customers',) instead of filter_horizontal = ('costumers',) In other words, the spelling of customers differs between your model and admin files.
Some of my m2m fields did not allow form_horizontal. I found that the only thing these specific fields had in common was that they were all for categories, in my native language this is categorieën, which happens to have an accent on the last e.
See my field definition below:
categories = models.ManyToManyField (
'FAQCategories',
verbose_name = 'Categorieën',
blank = True,
)
The problem here is that I did not tell python my string should be treated as Unicode. When I changed the verbose_name to u'Categoriën' it worked!
In retrospect this was very obvious to me but I hope this will help you or some else.. Define those strings properly, lesson learned!