Ignore POST value from admin inline - django

in django i have inlineadmin with some checkboxes, i want one of them to be ignored from saving if some condition applies.
I tried modifying request.POST['mykey'] or form.data['mykey'] in save_model() of main class with request.POST._mutable = True, but django save all anyway and for form.data it say is immutable.
I know i can set value for obj.mykey but how to save others and ignore one?
Many many thanks to all.
UPDATE
I build few lines of codes to explain better:
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
admin.py:
from django.contrib import admin
from testB.models import Book, Author
class BookInLine(admin.TabularInline):
model = Book
fields = ['author', 'title']
class AuthorAdmin(admin.ModelAdmin):
inlines = [BookInLine]
def save_model(self, request, obj, form, change):
if request.user.is_superuser:
#do not save first title and do not change existing value
# does not work
# request.POST._mutable = True
# request.POST['id_book_set-0-id']=''
# does not owrk: querydict immutable
# form.data['id_book_set-0-id']=''
pass
super().save_model(request, obj, form, change)
admin.site.register(Author, AuthorAdmin)
Using has_change_permission is not the way, because user should be able to change other title lines, this is a backend check.

Solved! Use save_formset() instead of save_model():
class AuthorAdmin(admin.ModelAdmin):
inlines = [BookInLine]
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for obj in formset.deleted_objects:
obj.delete()
for instance in instances:
if request.user.is_superuser and instance.title != 'my title':
instance.save()
formset.save_m2m()
admin.site.register(Author, AuthorAdmin)

Related

Store who updated Django Model from admin

I have a use case where data is only inserted and updated from django admin. Now, I have multiple users who have access to django admin page. I want to be able to store who exactly updated or created a record in django admin page.
Ideally, I want to add a separate column to an existing model.
models.py
class Links(models.Model):
link = models.URLField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = model.ForeignKey(UserModel)
updated_at = models.DateTimeField(auto_now=True)
updated_by = model.ForeignKey(UserModel)
You can override the .save_model(…) method [Django-doc] to set the .updated_by and .created_by fields, depending on whether change is True or False:
from django.contrib import admin
class LinkAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
obj.updated_by = request.user
return super().save_model(request, obj, form, change)
admin.site.register(Links, LinkAdmin)
If you need this for a large number of models, you can make a mixin, and then use that for all sorts of models:
class CreateUpdateUserAdminMixin:
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
obj.updated_by = request.user
return super().save_model(request, obj, form, change)
and then use the mixin with:
class LinkAdmin(CreateUpdateUserAdminMixin, admin.ModelAdmin):
pass
class OtherModelAdmin(CreateUpdateUserAdminMixin, admin.ModelAdmin):
pass
admin.site.register(Links, LinkAdmin)
admin.site.register(OtherModel, OtherModelAdmin)
Note: normally a Django model is given a singular name, so Link instead of Links.

What's the best way to perform actions before a Model.save() using Django?

I'm using Django-Rest-Framework(ViewSet approach) on my project interacting with a React app. So, I'm not using Django admin nor Django forms.
My project's structure is:
View
Serializer
Model
What I need to do is to perform actions before models method calls:
Insert the request.user on a Model field.
Start a printer process after a Model.save()
.....
I have read a lot about django-way to do on Django.docs and there, the things seems to be showed for a Django-Admin like project, which is not my case.
By other hand, by reading the Stack's answers about in other topics, the way to do seems to be like: "It will work, but, It's not the right way to do that".
According to Django's documentation, the best way to perform that supposed to be by using a new file, called admin.py, where I would to register actions binding to a Model which could support save, delete, etc., but, it's not clear if this approach is to do that or only for provide a Django-Admin way to perform an action.
# app/models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
user = models.ForeignKey(User)
content = models.TextField()
class Comment(models.Model):
post = models.ForeignKey(Post)
user = models.ForeignKey(User)
content = models.TextField()
# app/admin.py
from app.models import Post, Comment
from django.contrib import admin
class CommentInline(admin.TabularInline):
model = Comment
fields = ('content',)
class PostAdmin(admin.ModelAdmin):
fields= ('content',)
inlines = [CommentInline]
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
def save_formset(self, request, form, formset, change):
if formset.model == Comment:
instances = formset.save(commit=False)
for instance in instances:
instance.user = request.user
instance.save()
else:
formset.save()
admin.site.register(Post, PostAdmin)
According to the answers I have heard, the best way would use something like that on Models:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(MyModelForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj
What I want to know is:
What's the best way to to perform that actions, on which files, what's the best structure.
to insert a request.user on a Model field you can use the perform_create() method on your view class. for more information visit https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#associating-snippets-with-users which is exactly what u want!
I'm not sure what you mean by start a printer process, but you usually can override save() method on your model class for doing a process after saving a model instace.
https://www.django-rest-framework.org/api-guide/serializers/#overriding-save-directly
The best way I found to insert the request.user on the model, as a "created_by" field, was by inserting a hidden field on the model serializer with a default data, just like these:
my_field = serializers.HiddenField(default=serializers.CurrentUserDefault())
The CurrentUserDefault() is a function wich returns the user request onto serializer.
https://www.django-rest-framework.org/api-guide/validators/#advanced-field-defaults
For actions performing after/before a save/delete, what I chose to use Django Signals,wich works as a dispatcher of actions, a little like react's redux.
https://docs.djangoproject.com/en/2.2/topics/signals/
Thank you everybody for the helpful answers.

Call admin function from another admin in django

I have a products/admin.py as follows
# django imports
from django.contrib import admin
# model imports
from products.models import Product
from inventory.models import Inventory
class ProductAdmin(admin.ModelAdmin):
list_display = # some fields in a tuple
readonly_fields = # some more fields in a tuple
def save_model(self, request, obj, form, change):
if 'is_active' in form.data:
if form.data['is_active'] == 'on':
# create an Inventory if product doesn't exists.
obj.created_by = request.user
obj.updated_by = request.user
if change:
obj.updated_by = request.user
obj.save()
admin.site.register(Product, ProductAdmin)
I have a inventory/admin.py as follows
# django imports
from django.contrib import admin
# models imports
from inventory.model import Inventory, DispatchedInventory
class InventoryAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.created_by = request.user
obj.updated_by = request.user
if change:
obj.updated_by = request.user
obj.save()
readonly_fields = ['created_by', 'updated_by']
Now I want to call InventoryAdmin.save_model() inside ProductAdmin.save_model() when if form.data['is_active'] == 'on': is true.
My motivation behind it is that to create an inventory for the product when product is made active.
I know how to create by taking an instance of Inventory model itself like as follows( which will be last resort):
inventory_instance = Inventory.objects.get_or_create(product=product)
But calling the admin functions does all the job for me so I want to figure out a way to achieve it.
What I've tried so far is to pass InventoryAdmin as params:
class ProductAdmin(InventoryAdmin):
list_display = # some fields in a tuple
readonly_fields = # some more fields in a tuple
def save_model(self, request, obj, form, change):
if 'is_active' in form.data:
if form.data['is_active'] == 'on':
# couldn't figure out what to write here.
Any suggestions would be greatly appreciated! Thanks in advance.
Not, quite sure, if you are on the right track here, but if you want to call a method in an admin instance, you need to do something like this:
from django.contrib.admin.sites import AdminSite
adm = InventoryAdmin(Inventory, AdminSite())
inv = Inventory.objects.get_or_create(product=product)
adm.save_model(request, inv, form, change)
Not really worth the effort is it? This is three lines of code, the InventoryAdmin only has 6
But looking closely, I think I can spot a bug in InventoryAdmin,
obj.updated_by = request.user
if change:
obj.updated_by = request.user
You are updating the updated_by regardless of whether or not a change has happened, so that's another line shaved off!
Really, all you need to do in your ProductAdmin is
def save_model(self, request, obj, form, change):
if 'is_active' in form.data:
if form.data['is_active'] == 'on':
inv = Inventory.objects.get_or_create(product=obj,
created_by=request.user, updated_by=request_user)

unique_together and implicitly filled-in field in Django admin

Say I'm writing a multi-blog application and I want each author to use unique titles for their articles (but unique per user, not globally unique):
class Article(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=255)
#[...]
class Meta:
unique_together = (('title', 'owner'),)
Now, I want the author field to be auto-filled by the application:
class ArticleAdmin(ModelAdmin):
exclude = ('owner',)
def save_model(self, request, obj, form, change):
if not change:
obj.owner = request.user
obj.save()
Actually this does not work: if I try to create a new Article with an existing author-title combination, Django will not check the uniqueness (because author is excluded from the form) and I'll get an IntegrityError when it hits the database.
I thought of adding a clean method to the Article class:
def clean(self):
if Article.objects.filter(title=self.title, owner=self.owner).exists():
raise ValidationError(u"...")
But it seems that Article.clean() is called before ArticleAdmin.save_model(), so this does not work.
Several variants of this question have been asked already here, but none of the solutions seem to work for me:
I cannot use Form.clean() or other form methods that don't have the request available, since I need the request.user.
For the same reason, model-level validation is not possible.
Some answers refer to class-based views or custom views, but I'd like to remain in the context of Django's Admin.
Any ideas how I can do this without rewriting half of the admin app?
You are finding a way to bring request to customized form, in ModelAdmin, actually:
from django.core.exceptions import ValidationError
def make_add_form(request, base_form):
class ArticleForm(base_form):
def clean(self):
if Article.objects.filter(title=self.cleaned_data['title'], owner=request.user).exists():
raise ValidationError(u"...")
return self.cleaned_data
def save(self, commit=False):
self.instance.owner = request.user
return super(ArticleForm, self).save(commit=commit)
return ArticleForm
class ArticleAdmin(admin.ModelAdmin):
exclude = ('owner',)
def get_form(self, request, obj=None, **kwargs):
if obj is None: # add
kwargs['form'] = make_add_form(request, self.form)
return super(ArticleAdmin, self).get_form(request, obj, **kwargs)

Django User Fields on Models

I would like every model of my app to store the user that created its entries. What I did:
class SomeModel(models.Model):
# ...
user = models.ForeignKey(User, editable = False)
class SomeModelAdmin(admin.ModelAdmin):
# ...
def save_model(self, request, obj, form, change):
# Get user from request.user and fill the field if entry not existing
My question: As there's an entire app for User authentication and history, is there an easy way (perhaps, more oriented or standardized) of using any feature of this app instead of doing the above procedure to every model?
Update:
Here's what I did. It looks really ugly to me. Please let me know if there's a clever way of doing it.
I extended all the models i wanted to have these fieds on models.py:
class ManagerLog(models.Model):
user = models.ForeignKey(User, editable = False)
mod_date = models.DateTimeField(auto_now = True, editable = False, verbose_name = 'última modificação')
class Meta:
abstract = True
In admin.py, I did the same with the following class:
def manager_log_save_model(self, request, obj, form, change):
obj.user = request.user
return obj
Then, I also need to to override save_model on every extended model:
class ExtendedThing(ManagerLogAdmin):
# ...
def save_model(self, request, obj, form, change):
obj = manager_log_save_model(self, request, obj, form, change)
# ... other stuff I need to do here
more easy way,use save_model
class MyAdmin(admin.ModelAdmin):
...
def save_model(self, request, obj, form, change):
if getattr(obj, 'author', None) is None:
obj.author = request.user
obj.save()
see:https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_model
I think you should check out the django packages section on versioning. All those apps will track changes to your model, who made those changes and when.