Call admin function from another admin in django - 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)

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.

Ignore POST value from admin inline

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)

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.

how to provide a second model form into first model form if the data entered correct in first model form in django admin?

I have a very complex situation.
from types import *
from django import forms
from django.db.models import Q
from django.core.exceptions import ValidationError
from schdeules.models import tbmsttemplate,tbmstreviewsched,tbtrnrevdepartments,tbtrnrevdesignations,tbmstappraisalsched,tbtrnappraisalreview,tbmstdepartment,tbmstpart,tbmstsection,tbtrnappraisalquestion
class tbmstappraisalschedForm(forms.ModelForm):
def clean(self):
"""
Override the default clean method to check whether this course has been already inputted.
"""
cleaned_data = super(tbmstappraisalschedForm, self).clean()
depart_id = self.cleaned_data.get('intDeptID')
fromdate = str(self.cleaned_data.get('sdtFromDate'))
todate = str(self.cleaned_data.get('sdtToDate'))
pk=self.instance.pk
res = tbmstappraisalsched.objects.filter(
Q(sdtFromDate__lte=fromdate,sdtToDate__gte=fromdate) | \
Q(sdtFromDate__lte=todate,sdtToDate__gte=todate), ~Q(intAppSchedID=pk),
intDeptID=depart_id.pk,
)
if(res).exists():
raise ValidationError('The slot is present for selected dates and selected department')
else:
res_revs = tbmstreviewsched.objects.filter(
Q(sdtFromDate__lte=fromdate,sdtToDate__gte=fromdate) | \
Q(sdtFromDate__lte=todate,sdtToDate__gte=todate),
tbtrnrevdepartments__intDeptID=depart_id.pk,
)
if(res_revs).exists():
show review scheds such that user can select needed review scheds and save data in two models at once i.e; tbmstappraisalsched,tbtrnappraisalreview
return self.cleaned_data
else:
raise ValidationError('Reviewschedule slots are not present for the selected dates and selected department')
class Meta:
model = tbmstappraisalsched
in above code i have to get review scheds from tbmstreviewsched and show to user such that he can select required review sched and save the data in two models at once in such a way the data has to be saved in tbmstappraisalsched which returns intappschedid(autoincrement value) and then the data has to be saved in tbtrnappraisalreview with new intappschedid and selected review sched ids.
and my admin.py code is
from schdeules.models import tbmsttemplate,tbmstreviewsched,tbtrnrevdepartments,tbtrnrevdesignations,tbmstappraisalsched,tbtrnappraisalreview,tbmstpart,tbmstsection,tbtrnappraisalquestion
from django.core.exceptions import ValidationError
from forms import tbmstappraisalschedForm
class QuestionsInline(admin.StackedInline):
model = tbtrnappraisalquestion
extra = 0
class TemplatesAdmin(admin.ModelAdmin):
readonly_fields = ("intUpdatedBy",)
inlines = [QuestionsInline]
def save_model(self, request, obj, form, change):
obj.intUpdatedBy = request.user.id
obj.save()
class ReviewDesignationInline(admin.StackedInline):
model = tbtrnrevdesignations
extra = 1
class ReviewDepartmentInline(admin.StackedInline):
model = tbtrnrevdepartments
exclude = ['intUpdatedBy']
extra = 1
class ReviewScheduleAdmin(admin.ModelAdmin):
inlines = [ReviewDepartmentInline,ReviewDesignationInline]
exclude = ['intUpdatedBy']
def save_model(self, request, obj, form, change):
obj.intUpdatedBy = request.user.id
obj.save()
class AppraisalScheduleAdmin(admin.ModelAdmin):
exclude = ['intUpdatedBy']
form = tbmstappraisalschedForm
def save_model(self, request, obj, form, change):
obj.intUpdatedBy = request.user.id
obj.save()
class AppraisalReviewAdmin(admin.ModelAdmin):
exclude = ['intUpdatedBy']
def save_model(self, request, obj, form, change):
obj.intUpdatedBy = request.user.id
obj.save()
admin.site.register(tbmstpart)
admin.site.register(tbmstsection)
admin.site.register(tbmsttemplate,TemplatesAdmin)
admin.site.register(tbmstreviewsched,ReviewScheduleAdmin)
admin.site.register(tbmstappraisalsched,AppraisalScheduleAdmin)
admin.site.register(tbtrnappraisalreview,AppraisalReviewAdmin)

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.