Displaying ManyToManyField correctly in DjangoAdmin - django

I'm trying to add a Likes model to a Posts application so that it records every time a User 'likes' a determined post. I have already added the likes field to the Posts model as a ManyToManyField and have tried to register it to display in the Django Admin, more exactly in the Post detail, but the result of the code I have only displays a list of all users where it should only be a list of the users that have liked the corresponding post.
Here's the code for my Posts model:
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
# ForeignKey that relates the post to the user that published it and their corresponding profile.
user = models.ForeignKey(User, on_delete=models.PROTECT)
profile = models.ForeignKey('users.Profile', on_delete=models.PROTECT)
title = models.CharField(max_length=255)
photo = models.ImageField(upload_to='posts/pics')
description = models.TextField(max_length=2000, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
# ManyToManyField that translates to the users that have liked the post
likes = models.ManyToManyField(User, blank=True, related_name='post_likes')
def __str__(self):
return '{} by {}'.format(self.title, self.user.username)
def get_likes(self):
return '\n'.join([l.likes for l in self.likes.all()])
Also this is the code for the admin.py file.
#admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = (
'pk',
'user',
'title',
'photo',
'created',
'modified',
'get_likes',
)
list_display_links = ('pk', 'title',)
list_editable = ('photo',)
search_fields = (
'post__user',
'post__title'
)
list_filter = (
'created',
'modified'
)
fieldsets = (
('Profile', {
'fields' : (
'user',
)
}),
('Post', {
'fields' : (
'title',
'photo',
'likes',
)
})
)
How can I get the Django admin to display the ManyToManyField correctly in the post detail (i.e. display the users that have actually liked the post instead of a list of all users)?

Related

Set CharField to RTL in Django Admin site

The frontend of my Django site is in Persian language which is RTL and everything is ok except that the CharField model fields are in LTR direction when edited in the Admin site.
Here's my model:
class Post(models.Model):
STATUS_CHOICES = (('draft', 'Draft'), ('published', 'Published'))
title = models.CharField(max_length=100)
slug = models.SlugField(max_length=100, allow_unicode=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')
lead = RichTextField()
body = RichTextUploadingField()
created_on = models.DateTimeField(auto_now_add=True)
published_on = models.DateTimeField(default=timezone.now)
updated_on = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
is_featured = models.BooleanField(default=False, verbose_name='Featured Post')
objects = models.Manager()
published = PublishedManager()
featured = FeaturedManager()
class Meta:
ordering = ('-published_on',)
def __str__(self):
return self.title
I know I can set the site's language to Persian and solve this issue but I don't want to because the Persian translation of Django is dull.
Another solution is to use one of available Rich Text editors (tinymce or ckeditor) but those are overkill for a CharField field.
I also tried custom admin form like this:
class PostAdminForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'author', 'lead', 'body', 'status', 'is_featured']
widgets = {'title': forms.TextInput(attrs={'dir': 'rtl'})}
#admin.register(Post, PostAdminForm)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'created_on', 'published_on', 'status', 'is_featured')
list_filter = ('status', 'created_on', 'published_on', 'is_featured')
search_fields = ('title', 'body')
prepopulated_fields = {'slug': ('title',)}
raw_id_fields = ('author',)
date_hierarchy = 'published_on'
ordering = ('status', 'created_on', 'published_on')
But it gives me this error:
AttributeError: 'ModelFormOptions' object has no attribute 'abstract'
In your admin.py file for your app, you can create a custom form for your model. I don't know your model so I will use general names for this as an example:
from django.contrib import admin
from app_name.models import *
from django import forms
class CustomAdminForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['mycharfield']
widgets = {'mycharfield':forms.TextInput(attrs={'dir':'rtl'})}
admin.site.register(MyModel,CustomAdminForm)
This should make the mycharfield text input render as being RTL on the admin page for the MyModel form. The line widgets = {'mycharfield':forms.TextInput(attrs={'dir':'rtl'})} will change the text input widget's dir attribute to the rtl value. If you have more than one CharField in your model and want RTL for all of them simply add each field to the fields attribute in the form and do the same thing with the widget attribute for each field.
As the answer provided by Nathan was partially correct, I did my own research and found that the correct way is this:
class CustomPostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'lead', 'body']
widgets = {'title': forms.TextInput(attrs={'dir': 'rtl', 'class': 'vTextField'})}
and then:
#admin.register(Post)
class PostAdmin(admin.ModelAdmin):
form = CustomPostForm
What surprises me is that the above code removed vTextField class from the input field so I had to add it again.

FieldError: 'created' cannot be specified for Post model form as it is a non-editable field

Django 3.0.8
admin.py
class PostAdmin(admin.ModelAdmin):
list_display = ("id", "title", 'category',)
def get_fields(self, request, obj=None):
fields = [a_field.name for a_field in self.model._meta.fields]
return fields
def get_readonly_fields(self, request, obj):
readonly_fields = self.readonly_fields
if obj:
if obj.already_published and (not request.user.has_perm('posts.change_already_puclished')):
readonly_fields = READONLY_FIELDS_AFTER_PUBLISHING
return readonly_fields
traceback
Exception Type: FieldError
Exception Value:
'created' cannot be specified for Post model form as it is a non-editable field. Check fields/fieldsets/exclude attributes of class PostAdmin.
Local vars
Variable Value
change
False
defaults
{'exclude': None,
'fields': ['id',
'comment',
'title',
'slug',
'description',
'body',
'draft',
'already_published',
'featured_image',
'category',
'excerpt',
'h1',
'author',
'created',
'updated',
'sidebar',
'keywords',
'lsi',
'google_indexed',
'google_first_indexed',
'yandex_indexed',
'yandex_first_indexed'],
'form': <class 'django.forms.widgets.ModelForm'>,
'formfield_callback': functools.partial(<bound method BaseModelAdmin.formfield_for_dbfield of <posts.admin.PostAdmin object at 0x7f1d596e3820>>, request=<WSGIRequest: GET '/admin/posts/post/add/'>)}
exclude
None
excluded
None
fields
['id',
'comment',
'title',
'slug',
'description',
'body',
'draft',
'already_published',
'featured_image',
'category',
'excerpt',
'h1',
'author',
'created',
'updated',
'sidebar',
'keywords',
'lsi',
'google_indexed',
'google_first_indexed',
'yandex_indexed',
'yandex_first_indexed']
form
<class 'django.forms.widgets.ModelForm'>
kwargs
{}
new_attrs
{'declared_fields': {}}
obj
None
readonly_fields
()
request
<WSGIRequest: GET '/admin/posts/post/add/'>
self
<posts.admin.PostAdmin object at 0x7f1d596e3820>
The same as image:
I try this code both with a superuser and a user who don't have change_already_puclished permission.
Superuser: when trying to add or change a post, the error appear.
Ordinary user: adding posts is Ok, no errors. But adding a new pose causes this error.
Edit
class Post(DraftMixin,
TitleMixin,
TagMixin,
SlugMixin,
DescriptionMixin,
CommentMixin,
FeaturedImageMixin,
BodyMixin,
models.Model):
category = models.ForeignKey(Category,
on_delete=models.PROTECT,
verbose_name="Категория")
excerpt = models.TextField(default=True,
blank=True,
verbose_name="Выдержка") # A text associated to a Post. Most of the time, it is used as the Post summary.
h1 = models.CharField(max_length=500,
blank=True,
default="",
verbose_name="H1")
author = models.ForeignKey(Author,
blank=True,
null=True,
on_delete=models.PROTECT,
related_name='blog_posts',
verbose_name="Автор")
created = models.DateField(auto_now_add=True,
verbose_name="Дата создания")
updated = models.DateField(auto_now=True,
verbose_name="Дата обновления")
sidebar = models.ForeignKey(Sidebar,
null=True,
blank=True,
on_delete=models.PROTECT,
verbose_name="Сайдбар")
keywords = models.TextField(blank=True, default="", verbose_name="Ключевые слова")
lsi = models.TextField(blank=True, default="", verbose_name="LSI")
google_indexed = models.BooleanField(verbose_name="Индексировано Google",
default=False)
google_first_indexed = models.DateField(blank=True,
null=True,
verbose_name="Дата первой индексации Google")
yandex_indexed = models.BooleanField(verbose_name="Индексировано Яндекс",
default=False)
yandex_first_indexed = models.DateField(blank=True,
null=True,
verbose_name="Дата первой индексации Яндексом")
class Meta:
ordering = ('-created',)
verbose_name = "Статья"
verbose_name_plural = "Статьи"
permissions = [
("change_already_puclished", 'Может менять статус "Уже опубликовано"'),
]
The problem is caused by this definition:
created = models.DateField(auto_now_add=True,
verbose_name="Дата создания")
auto_now_add makes it so that Django can't edit it. So you can't specify it as an editable form field.
You need to specify it as a readonly field. See: Django admin: How to display a field that is marked as editable=False' in the model?
If you set auto_now_add to True, then the field will become un-editable by default (editable=False) and Django will handle that field automatically. Also auto_now=True will cause an override to any changes since it will update the field when you call save() on an instance.
You can add those fields to be shown in a form by adding them to readonly_fields but you can't add/edit them. Now you either have to remove auto_now_add and auto_now and provide your value for them or you should let Django do it for you.
Django docs: DateField.auto_now_add

Serializing a field of the related model in a model DRF

I have a serializer as follows:
class ImageSerializer(serializers.HyperlinkedModelSerializer):
prop_post = serializers.SlugRelatedField(queryset=PropertyPost.objects.all(),
slug_field='pk')
class Meta:
model = Image
fields = (
'url',
'photo',
'prop_post',
)
This works Ok. Now my PropertyPost has a 'owner' field that I need to include in my ImageSerializer.
I was wondering how could I do that. I was thinking it might be like
fields = (
'url',
'photo',
'prop_post',
'prop_post__owner'
)
but it didn't work. Any help is appreciated.
here is my model:
class PropertyPost(models.Model):
owner = models.ForeignKey(
get_user_model(),
related_name='posts4thisowner',
on_delete=models.CASCADE)
class CustomUser(AbstractUser):
pass
class Image(models.Model):
prop_post = models.ForeignKey(
PropertyPost,
related_name='images4thisproperty',
on_delete=models.CASCADE)
photo = models.ImageField(upload_to=upload_update_image, null=True, blank=True)
Easier to use 'source' and you can use your user serializer to populate the owner fields.
Example:
class ImageSerializer(serializers.HyperlinkedModelSerializer):
prop_post = serializers.SlugRelatedField(queryset=PropertyPost.objects.all(),
slug_field='pk')
owner = UserSerializer(source="prop_post.owner")
class Meta:
model = Image
fields = (
'url',
'photo',
'prop_post',
'owner',
)
Use SerializerMethodField() that will achieve the task.
Since you haven't posted your models and how it's related.
I gave you the normal idea of how to achieve this.
It will be better if you can add the models as well.
I will be able to update the answer accordingly.
from rest_framework.serializers import SerializerMethodField
class ImageSerializer(serializers.HyperlinkedModelSerializer):
prop_post = serializers.SlugRelatedField(queryset=PropertyPost.objects.all(),
slug_field='pk')
prop_post_title = SerializerMethodField()
class Meta:
model = Image
fields = [
'url',
'photo',
'prop_post',
'prop_post_title',
]
def get_prop_post_title(self, obj):
try:
return obj.prop_post.title
except:
return None

Can't add a choice form field on custom usercreateform

I've been trying to create a custom signup form using an extended user model in django. One of the custom fields I've been trying to add is a user type which can only be either "Employee" or "Admin".
My signUpForm class looks like this
from .models import EMPLOYEE_TYPE_CHOICES
class SignUpForm(UserCreationForm):
usertype = forms.CharField(
max_length=10,
choices=EMPLOYEE_TYPE_CHOICES,
)
userID = forms.CharField(label="User ID")
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2', 'userID', 'usertype')
EMPLOYEE_TYPE_CHOICES comes from my models.py which look like this
EMPLOYEE_TYPE_CHOICES = (
('admin', 'Admin'),
('employee', 'Employee'),
)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
ADMIN = 'Admin'
EMPLOYEE = 'Employee'
EMPLOYEE_TYPE_CHOICES = (
(ADMIN, 'Admin'),
(EMPLOYEE, 'Employee'),
)
usertype = models.CharField(
max_length=10,
choices=EMPLOYEE_TYPE_CHOICES,
)
userID = models.CharField(
max_length=10,
)
When running the server, I receive the error
TypeError: init() got an unexpected keyword argument 'choices'
Is there a different method for adding the choices to my form field?
The source of your error is due to using CharField instead of using ChoiceField. https://docs.djangoproject.com/en/1.11/ref/forms/fields/#choicefield
Even though what are you trying to accomplish wont work with the form created. You have to create a ModelForm for your Profile model. Then you can render the field simply on a template.

Show post in django admin page but don't on site

I'm creating simple blog and face with problem. I need to have separate type of posts in Django admin page which would be saved, but not showed on site. For example, when somebody suggest post, I want at first read it and after that publish, or when I'm writing a post and want go on after some time I need to save it.
blog/models.py
class Post(models.Model):
author = models.ForeignKey(User, default=1)
title = models.CharField(max_length = 50)
pub_date = models.DateTimeField(default=timezone.now)
content = models.TextField(max_length = 50000)
published = models.BooleanField(default=False)
def __str__(self):
return self.title
def get_absolute_url(self):
return "/blog/%i/" % self.pk
blog/admin.py
class PostAdmin(admin.ModelAdmin):
fieldsets = (
('Title', {'fields' : ['title']}),
('Date', {'fields' : ['pub_date'], 'classes' : ['collapse']}),
('Content', {'fields' : ['content']}),
)
list_display = ('title', 'pub_date')
list_filter = ['pub_date']
search_fields = ['title']
admin.site.register(Post, PostAdmin)
blog/views.py
class PostsListView(ListView):
model = Post
You can modify your list view to only show published posts by overriding get_queryset.
class PostsListView(ListView):
model = Post
def get_queryset(self):
return super(PostsListView, self).get_queryset().filter(published=True)
If you have a detail view, you should override get_queryset in the same way.
In your model admin, you can add published to list_filter. This makes it easy to filter published/unpublished posts.
class PostAdmin(admin.ModelAdmin):
...
list_filter = ['pub_date', 'published']