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.
Related
Newbie to DRF and have a model called posts. And another called user. The post object looks as follows:
class Post(models.Model):
"""
Post model
"""
title = models.CharField(max_length=250)
body = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='forum_posts')
parent_post = models.ForeignKey('self',
on_delete=models.CASCADE,
blank=True,
null=True)
time_stamp = models.DateTimeField(default=timezone.now)
objects = models.Manager()
The serializer for this model is:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = models.Post
fields = ('id', 'title', 'body', 'parent_post', 'author', 'time_stamp')
extra_kwargs = {'id': {'read_only': True},
'author': {'read_only': True}}
When returning data for this model, I want to add an extra attribute to each object within the query set called "author_username". The username should be the username belonging to the post's author id. I also want to do this without modifying the model to add another attribute such as "author_username" since this'll be redundant (already have an FK for author). So, ideally, the json for an object would look like:
'post_id': 1
'post_title': 'Example post'
'post_body': 'Example post'
'author_id': 1
'parent_post_id': null
'time_stamp': '2022'
'author_username': 'testUser'
How can I go about doing this?
Here's my view:
class PostList(generics.ListCreateAPIView):
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = models.Post.objects.all()
serializer_class = serializers.PostSerializer
The source argument can be passed to a serializer field to access an attribute from a related model
class PostSerializer(serializers.ModelSerializer):
author_username = serializers.CharField(source="author.username", read_only=True)
class Meta:
model = models.Post
...
You should add a select_related call to your view's queryset
class PostList(generics.ListCreateAPIView):
...
queryset = models.Post.objects.select_related('author')
...
I have the following models
class Breed(models.Model)::
name = models.CharField(max_length=200)
class Pet(models.Model):
owner = models.ForeignKey(
"User",
on_delete=models.CASCADE,
)
name = models.CharField(max_length=200)
breed = models.ForeignKey(
"Breed",
on_delete=models.CASCADE,
)
I am trying to add few fileds for representation purpose. I dont want them to be included while create or update
class PetSerializer(serializers.ModelSerializer):
owner_email = serializers.CharField(source='owner.email')
breed_name = serializers.CharField(source='breed.str')
class Meta:
model = Pet
fields = "__all__"
read_only_fields = ["breed_name","owner_email"]
This is not working. I see the owner_email and breed_name in the HTMLform (the DRF api page)
Where as
class PetSerializer(serializers.ModelSerializer):
owner_email = serializers.CharField(source='owner.email',read_only=True)
breed_name = serializers.CharField(source='breed.str',read_only=True)
class Meta:
model = Pet
fields = "__all__"
This is working. I dont see them in the HTMLform
Also i observed, if i use a model field directly in read_only_fields then it works.
class PetSerializer(serializers.ModelSerializer):
class Meta:
model = Pet
fields = "__all__"
read_only_fields = ["name"]
This will make all name not shown in update or create
Why read_only_fields is not working properly
This is very interesting. I looked into the code and found the root cause, specifically this lines in the implementation for ModelSerializer:
for field_name in field_names:
# If the field is explicitly declared on the class then use that.
if field_name in declared_fields:
fields[field_name] = declared_fields[field_name]
continue
....
Here was my script for the investigation
from django.db import models
from rest_framework import serializers
class MyModel(models.Model):
xero_contact_id = models.UUIDField(unique=True)
name = models.CharField(max_length=255, default="Some name")
class Meta:
db_table = "my_model"
class MySerializer(serializers.ModelSerializer):
owner_email = serializers.CharField()
breed_name = serializers.CharField(max_length=255)
class Meta:
model = MyModel
fields = '__all__'
read_only_fields = ["breed_name", "owner_email", "xero_contact_id"]
serializer = MySerializer()
print(repr(serializer))
I added some prints and here is what I saw:
>>> print(repr(serializer))
field_names ['id', 'owner_email', 'breed_name', 'xero_contact_id', 'name']
declared_fields OrderedDict([('owner_email', CharField()), ('breed_name', CharField(max_length=255))])
extra_kwargs {'breed_name': {'read_only': True}, 'owner_email': {'read_only': True}, 'xero_contact_id': {'read_only': True}}
MySerializer():
id = IntegerField(label='ID', read_only=True)
owner_email = CharField()
breed_name = CharField(max_length=255)
xero_contact_id = UUIDField(read_only=True)
name = CharField(max_length=255, required=False)
As you can see, the read_only argument is in the extra_kwargs. The problem is that for all the fields that are only declared in the ModelSerializer itself (visible from declared_fields) and not in the model class, they don't read from the extra_kwargs, they just read what was set in the field itself as visible in the code snippet above fields[field_name] = declared_fields[field_name] then performs a continue. Thus, the option for read_only was ignored.
I fixed it by modifying the implementation of ModelSerializer to also consider the extra_kwargs even for non-model fields
for field_name in field_names:
# If the field is explicitly declared on the class then use that.
if field_name in declared_fields:
field_class = type(declared_fields[field_name])
declared_field_args = declared_fields[field_name].__dict__['_args']
declared_field_kwargs = declared_fields[field_name].__dict__['_kwargs']
extra_field_kwargs = extra_kwargs.get(field_name, {})
# Old implementation doesn't take into account the extra_kwargs
# fields[field_name] = declared_fields[field_name]
# New implementation takes into account the extra_kwargs
fields[field_name] = field_class(*declared_field_args, **declared_field_kwargs, **extra_field_kwargs)
continue
....
Now, read_only was correctly set to the target fields, including non-model fields:
>>> print(repr(serializer))
field_names ['id', 'owner_email', 'breed_name', 'xero_contact_id', 'name']
declared_fields OrderedDict([('owner_email', CharField()), ('breed_name', CharField(max_length=255))])
extra_kwargs {'breed_name': {'read_only': True}, 'owner_email': {'read_only': True}, 'xero_contact_id': {'read_only': True}}
MySerializer():
id = IntegerField(label='ID', read_only=True)
owner_email = CharField(read_only=True)
breed_name = CharField(max_length=255, read_only=True)
xero_contact_id = UUIDField(read_only=True)
name = CharField(max_length=255, required=False)
This doesn't seem to be in the DRF docs. Sounds like a feature we can request to DRF :) So the solution for the meantime is as what #JPG pointed out, use read_only=True explicitly in the extra non-model fields.
The read_only_fields meta option will work for the fields which are not explicitly defined in the Serializer.
So, in your case, you need to add the read_only=True to those explicitly defined fields, as
class PetSerializer(serializers.ModelSerializer):
owner_email = serializers.CharField(source='owner.email', read_only=True)
breed_name = serializers.CharField(source='breed.str', read_only=True)
class Meta:
model = Pet
fields = "__all__"
I have written this model.
class Course(TranslatableModel):
translations = TranslatedFields(
title = models.CharField(max_length=200),
overview = models.TextField(),
slug = models.SlugField(max_length=200, unique=True))
owner = models.ForeignKey(User, related_name='courses_created')
subject = models.ForeignKey(Subject, related_name='courses')
created = models.DateTimeField(auto_now_add=True)
order = OrderField(blank=True, for_fields=['title'])
class Meta:
ordering = ('order',)
def __unicode__(self):
return self.title
AND ALSO THIS mixin class
class OwnerCourseEditMixin(OwnerCourseMixin, OwnerEditMixin):
fields = ['subject', 'title', 'slug', 'overview']
success_url = reverse_lazy('manage_course_list')
template_name = 'courses/manage/course/form.html'
The "fields = ['subject', 'title', 'slug', 'overview']" line is causing the error
Exception Type: FieldError
Exception Value: Unknown field(s) (overview, slug, title) specified for Course
How do I refer to the translated fields? If I remove 'title', 'slug', 'overview' from the fields list it works.
SOLVED
When translated fields are created django-parler creates a model for each translatable model. So, the model to work with is not the Course model itself, but the generated CourseTranslation model.
I still needed to add the subject field to the Translation model, then all worked.
I have two TextField in my model. But i want to change the rows and cols attribute of only of the TextField. I know from this page that the look of TextField can be changed using the following code in the admin.py.
class RulesAdmin(admin.ModelAdmin):
formfield_overrides = {
models.TextField: {'widget': Textarea(
attrs={'rows': 1,
'cols': 40})},
}
...
admin.site.register(Rules, RulesAdmin)
As I said, using the above code changed the look of both the TextField, I want only to change one of those here is how my model looks like:
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
update_date = models.DateTimeField(auto_now=True)
meta = models.TextField(blank=True)
def __str__(self):
return self.title
I want only to change the look of meta field.
How should I go about this ?
Add the following code to forms.py
from django.forms import ModelForm, Textarea
from .models import Lesson
class PostModelForm(ModelForm):
class Meta:
model = Lesson
fields = ('__all__')
widgets = {
'meta': Textarea(attrs={'cols': 80, 'rows': 5}),
}
and in admin.py do this
class PostModel(admin.ModelAdmin):
list_display = ('id', 'title', 'pub_date', 'course',)
search_fields = ('title', 'course__alias',)
form = PostModelForm
I would like to be able add multiple records at a time in django admin.
models.py
class Photo(models.Model):
pub_date = models.DateTimeField('date published')
sort = models.IntegerField()
photo_category = models.ForeignKey(Photocategory)
title = models.CharField(max_length=200)
title_url = models.SlugField(max_length=200)
main_image = models.ImageField(blank=True, null=True, upload_to='images/photo/')
# size is "width x height"
cropping = ImageRatioField('main_image', '350x350')
def image_thumbnail(self):
return '<img width="160px" src="/media/%s"/>' % (self.main_image, self.main_image)
image_thumbnail.allow_tags = True
image_thumbnail.short_description = 'main_image'
def __unicode__(self):
return self.title
admin.py
class PhotoAdmin(ImageCroppingMixin, admin.ModelAdmin):
prepopulated_fields = {'title_url': ('title',)}
list_display = ('title', 'photo_category', 'image_thumbnail', 'sort')
list_filter = ['photo_category']
admin.site.register(Photo, PhotoAdmin)
Screenshot:
Is there a way I could do lets say 5 at a time on the 1 screen, it is very slow filling that out 1 by 1. I can do it in mysql with SQL query but I would like to achieve this here.
Thanks
Hey Yes this is possible but Out of the Box,,
Here is what you will have to do:
Define a Formset (to create multiple photo forms) in form Attribute of PhotoAdmin
You will see a formset on the add photo Admin page, and should work correctly. If you don't then you can over-ride the templates by providing add_form_template and change_form_template attributes in Admin and display that form-set templates.
Handle Saving of Multiple objects in the Formset
For example:
# admin.py
#admin.register(Photo)
class PhotoAdmin(ImageCroppingMixin, admin.ModelAdmin):
prepopulated_fields = {'title_url': ('title',)}
list_display = ('title', 'photo_category', 'image_thumbnail', 'sort')
list_filter = ['photo_category']
form = MyFormset
add_form_template = "photo/admin/my_add_form.html"
change_form_template = "photo/admin/my_change_form.html"
This is a rough workflow, you will have to try this and make modification if required.
You can refer to this documentation: https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#modeladmin-options