Store who updated Django Model from admin - django

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.

Related

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)

Django - Setting default owner as the logged in user

This is a bump post. I have tried various ways and went through all examples here:
https://stackoverflow.com/a/4672123/6274043
https://stackoverflow.com/a/5529770/6274043
How do you specify a default for a Django ForeignKey Model or AdminModel field?
and all doesn't seem to work for me.
# models.py in main/ app
from django.contrib.auth.models import User
class Mileage(models.Model):
owner = models.ForeignKey(User)
date = models.DateField()
... #other parameters
#admin.py
class MileageAdmin(admin.ModelAdmin):
list_display = ['date', ...]
def save_model(self, request, instance, form, change):
user = request.user
instance = form.save(commit=False)
instance.owner= user
instance.save()
form.save_m2m()
return instance
#views.py
def home(request):
form = MileageForm(request.POST or None)
context = {"form": form}
if form.is_valid():
instance = form.save()
form = MileageForm()
context = {"form": form}
return render(request, 'record_trip.html', context)
I am trying to set the default owner as the logged in user. I tried doing it in ModelAdmin like how other posts do but doesn't seem to work. Can any kind soul point out where am I doing it wrong?
It keeps throwing IntegrityError owner_id cannot be null.
Please pardon me if I made elementary mistakes above. I started coding half a year ago for leisure.
Not sure if I understood the question correctly. You are trying to set the logged-in user as the default owner of a newly created model?
Currently, you are only setting created_by and modified_by to default to the logged-in user.
#admin.py
class MileageAdmin(admin.ModelAdmin):
...
def save_model(self, request, instance, form, change):
user = request.user
instance = form.save(commit=False)
if not change or not instance.created_by:
instance.created_by = user
instance.modified_by = user
instance.save()
form.save_m2m()
return instance
But those two fields do not even exist in you example model
class Mileage(models.Model):
owner = models.ForeignKey(User)
date = models.DateField()
... #other parameters
However, you are never actually setting instance.owner to be the request.user. So instance.owner remains to be None, that's why Django complains.
You need to set it before save()ing your instance.
class MileageAdmin(admin.ModelAdmin):
...
def save_model(self, request, instance, form, change):
instance.owner = request.user
if not change or not instance.created_by:
instance.created_by = request.user
instance.modified_by = request.user
instance.save()
form.save_m2m()
return instance
It sounds like the model's save method is getting called somewhere before you've had a chance to set it to the request.user.
You could either find out where that's happening (and perhaps other answers might have a better idea of where that's happening?) or if you wanted a shortcut, you could set the owner attribute to not be a required field, trusting that you'll set it later.
owner = models.ForeignKey(User, blank=True, null=True)
That way, creating the model without the owner wouldn't throw up an integrity error, but you could trust that the owner attribute would eventually be set.

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)

How can I get current logged User id in Django Admin Panel?

I've got model Message and it's form manager. To fill fields "user" and "groups" I need to know current logged user id, but I have no idea how to obtain it before save.
class Message(models.Model):
title = models.CharField(max_length = 100)
text = models.TextField()
date = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null = True, blank = True)
main_category = models.ForeignKey(MainCategory)
sub_category = models.ForeignKey(SubCategory)
groups = models.ManyToManyField(Group)
class MessageAdminForm(forms.ModelForm):
def __init__(self, *arg, **kwargs):
super(MessageAdminForm, self).__init__(*arg, **kwargs)
self.initial['main_category'] = MainCategory.objects.get(title = 'News')
Don't do that in the form. Override the save_model method on your admin subclass - it has access to the request.
class MessageAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.user = request.user
super(MessageAdmin, self).save(request, obj, form, change)
Edit: Daniel's way is better.
In your view:
user = request.user
if user.is_authenticated():
user_id=user.pk # pk means primary key
But you don't usually deal with the ID. Set the User field to be the object, not the id. Here's a snippet from something I'm working on at the moment:
def question_submit(request):
u = request.user
if u.is_authenticated():
if q.is_valid():
f=q.save(commit=False)
f.user=u
f.save()
return JsonResponse({'success': True})
to avoid ERROR --- 'super' object has no attribute 'save' To resolve use ---
use this:
def save_model(self, request, obj, form, change):
obj.user = request.user
super(MessageAdmin, self).save_model(request, obj, form, change)

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.