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)
Related
I want to implement the "Save as new" feature in Django's admin for a model such as this one:
class Plasmid (models.Model):
name = models.CharField("Name", max_length = 255, blank=False)
other_name = models.CharField("Other Name", max_length = 255, blank=True)
selection = models.CharField("Selection", max_length = 50, blank=False)
created_by = models.ForeignKey(User)
In the admin, if the user who requests a Plasmid object is NOT the same as the one who created it, some of the above-shown fields are set as read-only. If the user is the same, they are all editable. For example:
class PlasmidPage(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if obj:
if not request.user == obj.created_by:
return ['name', 'created_by',]
else:
return ['created_by',]
else:
return []
def change_view(self,request,object_id,extra_context=None):
self.fields = ('name', 'other_name', 'selection', 'created_by',)
return super(PlasmidPage,self).change_view(request,object_id)
The issue I have is that when a field is read-only and a user hits the "Save as new" button, the value of that field is not 'transferred' to the new object. On the other hand, the values of fields that are not read-only are transferred.
Does anybody why, or how I could solve this problem? I want to transfer the values of both read-only and non-read-only fields to the new object.
Did you try Field.disabled attribute?
The disabled boolean argument, when set to True, disables a form field using the disabled HTML attribute so that it won’t be editable by users. Even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data.
I did a quick test in my project. When I added a new entry the disabled fields were sent to the server.
So something like this should work for you:
class PlasmidPage(admin.ModelAdmin):
def get_form(self, request, *args, **kwargs):
form = super(PlasmidPage, self).get_form(request, *args, **kwargs)
if not request.user == self.cleaned_data['created_by'].:
form.base_fields['created_by'].disabled = True
form.base_fields['name'].disabled = True
def change_view(self,request,object_id,extra_context=None):
self.fields = ('name', 'other_name', 'selection', 'created_by',)
return super(PlasmidPage,self).change_view(request,object_id)
It happens because Django uses request.POST data to build a new object, but readonly fields are not sent with the request body. You can overcome this by making widget readonly, not the field itself, like this:
form.fields['name'].widget.attrs = {'readonly': True}
This has a drawback: it's still possible to change field values by tampering the form (e.g if you remove this readonly attribute from the widget using devtools console). You could protect from that by checking that values haven't actually changed in clean() method.
So full solution will be:
class PlasmidForm(models.ModelForm):
class Meta:
model = Plasmid
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and not self.instance.created_by == request.user:
self.fields['name'].widget.attrs = {'readonly': True}
def clean(self):
cleaned_data = super().clean()
if self.instance and not self.instance.created_by == request.user:
self.cleaned_data['name'] = instance.name # just in case user tampered with the form
return cleaned_data
class PlasmidAdmin(admin.ModelAdmin):
form = PlasmidForm
readonly_fields = ('created_by',)
def save_model(self, request, obj, form, change):
if obj.created_by is None:
obj.created_by = request.user
super().save_model(request, obj, form, change)
Notice I left created_by to be readonly, and instead populate it with the current user whenever object is saved. I don't think you really want to transfer this property from another object.
Hi I need to be able to add the current user to an inline object as it is being saved or modified. I am using django-admin dashboard as this application is not public facing.
class Med(models.Model):
generic_name = models.CharField(max_length=33)
last_updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL)
def save_model(self, request, obj, form, change):
try:
obj.last_updated_by = request.user
except AttributeError:
obj.last_updated_by = None
super().save_model(request, obj, form, change)
class Comment(models.Model):
text = models.TextField(("Comment"), max_length = 1000, null=False)
med = models.ForeignKey(Med, related_name="comments", on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL)
def save_model(self, request, obj, form, change):
obj.user = request.user
super().save_model(request, obj, form, change)
class CommentInline(admin.TabularInline):
model = Comment
extra = 0
class Med(admin.ModelAdmin):
inlines = (CommentInline,)
I have tried to override the save_related function as well, but it seems that the CommentFormSet objects it contains are ALL of them vs just the one being modified or saved:
'_queryset': <QuerySet [<Comment: test>, <Comment: another test>]>,
A few of the SO posts on this topic were stale and didn't have enough information to extrapolate a working save_related implementation either.
I think the method you are looking for overriding is save_formset. This method is called once per inline in your AdminModel, and saves the inline objects.
You could use it like this:
class Med(admin.ModelAdmin):
inlines = (CommentInline,)
def save_formset(self, request, form, formset, change):
for inline_form in formset.forms:
if inline_form.has_changed():
inline_form.instance.user = request.user
super().save_formset(request, form, formset, change)
This would add current user to those objects that are being modified.
Heads up, this solution also worked for me:
class MedAdmin(admin.ModelAdmin):
inlines = (CommentInline,)
def save_related(self, request, form, formsets, change):
for formset in formsets:
list_comment = formset.save(commit=False)
for comment in list_comment:
comment.user = request.user
return super().save_related(request, form, formsets, change)
I have made a django admin form to add a new field to the model and update a generic model, my code is below. Its all working perfectly accept for saving the current logged in user. In the save() method i cannot access request.user to populate created_by field.
class EventAdminForm(forms.ModelForm):
tag_it = forms.CharField(max_length=100)
class Meta:
model = Event
# Step 2: Override the constructor to manually set the form's latitude and
# longitude fields if a Location instance is passed into the form
def __init__(self, *args, **kwargs):
super(EventAdminForm, self).__init__(*args, **kwargs)
# Set the form fields based on the model object
if kwargs.has_key('instance'):
instance = kwargs['instance']
self.initial['tag_it'] = ', '.join([i.slug for i in instance.tags.all()])
def set_request(self, request):
self.request = request
# Step 3: Override the save method to manually set the model's latitude and
# longitude properties based on what was submitted from the form
def save(self, commit=True):
model = super(EventAdminForm, self).save(commit=False)
for i in self.cleaned_data['tag_it'].split(','):
model.tags.create(slug=i, created_by=User.objects.get(username='mazban'))
if commit:
model.save()
return model
class EventForm(admin.ModelAdmin):
exclude = ('published_by', 'published_at', 'updated_at', 'updated_by', )
form = EventAdminForm
Taking from #brandon response and your comment, you can mix them doing:
# admin.py
# don't override EventAdminForm's save(). Instead implement it here:
class EventAdmin(admin.ModelAdmin):
exclude = ('published_by', 'published_at', 'updated_at', 'updated_by', )
form = EventAdminForm
def save_model(self, request, obj, form, change):
obj.save()
obj.tags.all().delete()
for i in form.cleaned_data['tag_it'].split(','):
obj.tags.create(slug=i, created_by=request.user)
To get access to the request in admin, you need to override the save_model method of your ModelAdmin:
Example:
def save_model(self, request, obj, form, change):
if not change:
obj.author = request.user
obj.save()
For more information, check the docs: https://docs.djangoproject.com/en/1.3/ref/contrib/admin/#modeladmin-methods
I've a modelform and I excluded two fields, the create_date and the created_by fields. Now I get the "Not Null" error when using the save() method because the created_by is empty.
I've tried to add the user id to the form before the save() method like this: form.cleaned_data['created_by'] = 1 and form.cleaned_data['created_by_id'] = 1. But none of this works.
Can someone explain to me how I can 'add' additional stuff to the submitted modelform so that it will save?
class Location(models.Model):
name = models.CharField(max_length = 100)
created_by = models.ForeignKey(User)
create_date = models.DateTimeField(auto_now=True)
class LocationForm(forms.ModelForm):
class Meta:
model = Location
exclude = ('created_by', 'create_date', )
Since you have excluded the fields created_by and create_date in your form, trying to assign them through form.cleaned_data does not make any sense.
Here is what you can do:
If you have a view, you can simply use form.save(commit=False) and then set the value of created_by
def my_view(request):
if request.method == "POST":
form = LocationForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.created_by = request.user
obj.save()
...
...
`
If you are using the Admin, you can override the save_model() method to get the desired result.
class LocationAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.created_by = request.user
obj.save()
Pass a user as a parameter to form constructor, then use it to set created_by field of a model instance:
def add_location(request):
...
form = LocationForm(user=request.user)
...
class LocationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(forms.ModelForm, self).__init__(*args, **kwargs)
self.instance.created_by = user
The correct solution is to pass an instance of the object with pre-filled fields to the model form's constructor. That way the fields will be populated at validation time. Assigning values after form.save() may result in validation errors if fields are required.
LocationForm(request.POST or None, instance=Location(
created_by=request.user,
create_date=datetime.now(),
))
Notice that instance is an unsaved object, so the id will not be assigned until form saves it.
One way to do this is by using form.save(commit=False) (doc)
That will return an object instance of the model class without committing it to the database.
So, your processing might look something like this:
form = some_form(request.POST)
location = form.save(commit=False)
user = User(pk=1)
location.created_by = user
location.create_date = datetime.now()
location.save()
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.