In Django, I need to validate unique_together the author and title fields.
The problem is that the author is the request.user so what is the best approach to validate the admin form?
I have this admin:
#admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
exclude = ('author',)
def save_model(self, request, obj, form, change):
"""Save ``author`` as request user."""
if getattr(obj, 'author', None) is None:
obj.author = request.user
super().save_model(request, obj, form, change)
I can query inside the save_model() and filter both author and title but that doesn't really work well.
I also tried with a forms.ModelForm but I can't manage to get the request.user inside the clean() method.
This is my model:
class Document(models.Model):
title = models.CharField(max_length=32, blank=True)
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
class Meta:
unique_together = (('title', 'author'),)
Thank you.
Put a unique_together constrain on your model
class Foo(models.Model):
field_1 = models.CharField(max_length=50)
field_2 = models.CharField(max_length=50)
class Meta:
unique_together = ('field_1', 'field_2')
Django will automatically do the validation for you, if it fails it will throw an IntegrityError (from django.db import IntegrityError)
The author field cannot be excluded from the admin. The solution is to hide the author using the get_form method. The save_model method is still useful if someone tries to change the hidden input value.
#admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
# exclude = ('author',)
def get_readonly_fields(self, request, obj=None):
"""Make ``author`` field readonly on update."""
return ['author'] if obj else []
def get_form(self, request, obj=None, **kwargs):
"""Hide ``author`` selection default to request user on create."""
form = super().get_form(request, obj, **kwargs)
if not obj:
form.base_fields['author'].initial = request.user
form.base_fields['author'].widget = forms.HiddenInput()
return form
def save_model(self, request, obj, form, change):
"""Save ``author`` field as request user on create."""
if getattr(obj, 'author') != request.user and not change:
obj.author = request.user
super().save_model(request, obj, form, change)
Related
I need to autopopulate a field in admin site and make foreignkey field non editable with current_user :
views.py:
def my_view(request):
obj = model.objects.first()
response = HttpResponse(file, content_type='
application/vnd.ms-excel',
)
return response
urls.py:
path('temo/fill',views.my_view,name = 'my-view')
models.py
class Model(BaseModel, SingletonModel):
file = models.FileField(
upload_to='',
validators=[FileExtensionValidator([''])]
)
person_uploaded = models.ForeignKey(
'somemodel',
related_name='s',
null=True,
on_delete=models.SET_NULL,
)
admin.py:
#admin.register(tTemplate)
class TemplateAdmin(admin.ModelAdmin):
list_display = ('file','person_uploaded',)
readonly_fields = ('person_uploaded',)
def save(self, request):
if not self.id:
self.person_uploaded = request.user
super().save()
Well to prepopulate a field in django admin its straight forward doing something like so:
#admin.register(tTemplate)
class TemplateAdmin(admin.ModelAdmin):
list_display = ('file','person_uploaded',)
readonly_fields = ('person_uploaded',)
def save_model(self, request, obj, form, change):
obj.user = request.user
super().save_model(request, obj, form, change)
I have a model called Client with user field as a foreign key:
class Client(models.Model):
name = models.CharField(_('Client Name'), max_length=100)
address = models.CharField(_('Client Address'), max_length=100, blank=True)
demand = models.PositiveIntegerField(_('Client Demand'))
location = models.PointField(_('Client Location'))
created_at = models.DateTimeField(auto_now=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
class Meta:
default_permissions = ('add', 'change', 'delete', 'view')
def __str__(self):
return self.name
I want to limit the choice of the user field in the admin form based on who logged in
for example, here I logged in as agung, so I want the select box choice of user field limit only to agung, but here I can access other username like admin and rizky.
I tried this
class ClientAdminForm(forms.ModelForm):
class Meta:
model = Client
fields = "__all__"
def __init__(self, request, *args, **kwargs):
super(ClientAdminForm, self).__init__(request, *args, **kwargs)
if self.instance:
self.fields['user'].queryset = request.user
but it seems that it can't take request as an argument (I guess because this is not an Http request)
You can overwrite your Admin Model's get_form method to add the current request.user as class property. Next you can read it in the Form's constructor and filter the query.
class ClientAdmin(admin.ModelAdmin):
# [...]
def get_form(self, request, obj=None, **kwargs):
form_class = super(ClientAdmin, self).get_form(request, obj, **kwargs)
form_class.set_user(request.user)
return form_class
class ClientAdminForm(forms.ModelForm):
# [...]
#classmethod
def set_user(cls, user):
cls.__user = user
def __init__(self, *args, **kwargs):
super(ClientAdminForm, self).__init__(*args, **kwargs)
self.fields['user'].queryset = \
self.fields['user'].queryset.filter(pk=self.__user.pk)
However, is easiest exclude this field in form and update it in the save_model method:
class ClientAdmin(admin.ModelAdmin):
# [...]
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
You can do it by override the base_fields attribute of your form instance like this :
views.py
# Before instantiate the form class
ClientAdminForm.base_fields['user'] = forms.ModelChoiceField(queryset=self.request.user)
# Now you can instantiate the form
form = ClientAdminForm(...)
NB : Do override the base_fields just before instantiate the form
Help please. There are several admins who have different rights. There is a model where they can add a product. I want to make sure that every administrator sees what they have added themselves. In the database table there is a row сreated_by. For example, I add my books to the database and another administrator adds his books. each administrator have to sees what he added.
Thow do I do this?
model.py
class MyBooks(models.Model):
book = models.ForeignKey(Books, on_delete=models.CASCADE, blank=True, null=True, default=None)
fomil = models.CharField('Фомил',max_length=100, blank=True, null=True, default=None)
name= models.CharField('Ном',max_length=100, blank=True, null=True, default=None)
is_active = models.BooleanField('Ичозати таблиг (фаъол)',default=True)
created = models.DateTimeField('Санади сохташуда', auto_now_add=True, auto_now=False, )
updated = models.DateTimeField('Санади азнавшуда',auto_now_add=False, auto_now=True)
admin.py
class BooksAdmin(admin.ModelAdmin):
list_display = [field.name for field in MyBooks._meta.fields]
def save_model(self, request, obj, form, change):
if not obj.created_by:
obj.created_by = request.user
obj.save()
class Meta:
model = MyBooks
Just need to override the queryset function in your ModelAdmin to filter out those that weren't created by the requesting user.
#admin.Register(MyBooks)
class BooksAdmin(admin.ModelAdmin):
list_display = [field.name for field in MyBooks._meta.fields]
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(created_by=request.user)
def save_model(self, request, obj, form, change):
obj.created_by = request.user
super().save_model(request, obj, form, change)
class Meta:
model = MyBooks
here are more conditions for filtering.
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.groups.filter(name='useradmin').exists():
return qs.filter(created_by=request.user)
else:request.user.groups.filter(name='mainadmin').exists()
return qs
I have the following model:
class Test(models.Model):
name = models.CharField(max_length=100)
And the admin:
class TestForm(forms.ModelForm):
confirm_name = forms.CharField(max_length=100)
...
#admin.register(Test)
class TestAdmin(admin.ModelAdmin):
form = TestForm
fields = ('name',)
create_fields = ('name', 'confirm_name')
def get_fields(self, request, obj=None):
fields = super().get_fields(request, obj)
if not obj:
fields = self.create_fields
return fields
Everything works fine. But when you add a record and then try to edit it, I get the error "Please correct the error below." without showing any field errors. I checked the form errors and it says confirm_name should not be empty. Why is it still being included if it's not added in fields?
You need to use add_form instead of get_fields.
In your example
class CreateTestForm(forms.ModelForm):
name = models.CharField(max_length=100)
confirm_name = models.CharField(max_length=100)
#.... your validation logic
class UpdateTestForm(forms.ModelForm):
name = models.CharField(max_length=100)
and in your admin.py
#admin.register(Test)
class TestAdmin(admin.ModelAdmin):
form = TestForm
add_form = CreateTest
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during test creation
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
Credits from django.contrib.auth.admin
Field for admin form are from TestForm
class TestForm(forms.ModelForm):
confirm_name = forms.CharField(max_length=100)
You can use:
class CreateTestForm(forms.ModelForm):
confirm_name = forms.CharField(max_length=100)
class Meta:
model = Model
field = ('name', 'confirm_name')
class UpdateTestForm(forms.ModelForm):
class Meta:
model = Model
field = ('name',)
#admin.register(Test)
class TestAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, change=False, **kwargs):
if obj:
kwargs['form'] = UpdateTestForm
else:
kwargs['form'] = CreateTestForm
return super().get_form(request, obj, change, **kwargs)
In my models i have this class:
class temp_main(models.Model):
descr = models.CharField(max_length=200, verbose_name="Description")
notes = models.TextField(null=True, blank=True, verbose_name="Note")
dt = models.DateTimeField(auto_now=True, verbose_name="Created")
owner = models.ForeignKey('auth.User', related_name='tmain_owner', on_delete=models.CASCADE, verbose_name="API Owner")
class Meta:
verbose_name = '1-Main Template'
verbose_name_plural = '1-Main Templates'
def __str__(self):
return self.descr
Then in my admin.py
class temp_mainAdmin(admin.ModelAdmin):
#list_filter = ('main_id__descr', 'l_type')
list_display = ('descr', 'notes', 'dt')
#ordering = ('-l_type',)
def save_model(self, request, obj, form, change):
obj.user = request.user
super(temp_mainAdmin, self).save_model(request, obj, form, change)
def changeform_view(self, request, obj_id, form_url, extra_context=None):
l_mod = temp_main.objects.latest('id')
extra_context = {
'lmod': l_mod,
}
return super(temp_mainAdmin, self).changeform_view(request, obj_id, form_url, extra_context=extra_context)
all done but i would in add and edit form that my field descr (charfield) vas not displayed as a textbox but instead like a combobox with pre-defined key/val data like 1:'Descr1',2:Descr2' etc etc
How can i achieve this result in django admin add and edit form?
thanks in advance
You need to override the ModelAdmin.get_form() method, which will allow you to change the type of input field that Django uses by default for your descr field. Here's what it should look like:
from django.forms import SelectMultiple
class temp_mainAdmin(admin.ModelAdmin):
# No changes to the code you provided above / this is all the same:
#list_filter = ('main_id__descr', 'l_type')
list_display = ('descr', 'notes', 'dt')
#ordering = ('-l_type',)
def save_model(self, request, obj, form, change):
obj.user = request.user
super(temp_mainAdmin, self).save_model(request, obj, form, change)
def changeform_view(self, request, obj_id, form_url, extra_context=None):
l_mod = temp_main.objects.latest('id')
extra_context = {
'lmod': l_mod,
}
return super(temp_mainAdmin, self).changeform_view(request, obj_id, form_url, extra_context=extra_context)
# This is new - override the parent class's get_form method:
def get_form(self, request, obj=None, **kwargs):
# 1. Get the form from the parent class:
form = super(temp_mainAdmin, self).get_form(request, obj, **kwargs)
# 2. Change the widget:
form.base_fields['descr'].widget = SelectMultiple(choices=(
(1, 'Descr1'),
(2, 'Descr2'),
))
# 3. Return the form!
return form