I have a model with an ImageField, which allow an image upload from the panel admin of Django.
I would like to check if the image already exists, before saving the model.
If it's the case, I would like to display a popup (or a warning on the same page) with both images, to allow users to compare images, and allow saving if it's a false positive.
For the image comparison, I'm going to use imagehash.average_hash() algorithm which gaves me good results from my tests.
So my questions are:
How to get the file content (to compute the aHash), before the model save.
How to display a popup or modify the modelAdmin page to allow the check of false positive.
Any help is appreciated!
I figured out to achieve this a part of this:
admin.py
# called after page validation, to switch page (we catch the ValidationError send from save_model
# if this image was already uploaded
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
try:
return super(MediaAdmin, self).changeform_view(request, object_id, form_url, extra_context)
except ValidationError as e:
self.message_user(request, e, level=messages.ERROR)
if object_id:
# if we are editing an existing media
return redirect(to=reverse('admin:web_app_media_change', args=(object_id, )))
else:
# if we try to add a new media
return redirect(to=reverse('admin:web_app_media_add'))
# check if the media was uploaded before, and raise a ValidationError if it's the case
def save_model(self, request, obj, form, change):
if not obj.hash:
obj.hash = imagehash.average_hash(Image.open(obj.media)).__str__()
duplicate = False
for media in Media.objects.all():
if media.media != obj.media and media.hash == obj.hash:
duplicate = True
if not duplicate:
super(MediaAdmin, self).save_model(request, obj, form, change)
else:
raise ValidationError("This media was already uploaded.")
With a new hash = models.CharField(max_length=20) in my Media model.
Hoping it will help :)
Related
While clicking on save button multiple times. Multiple entries are getting created in db. How can i put a check there.
When overriding ModelAdmin.save_model() and ModelAdmin.delete_model(), your code must save/delete the object. They aren’t meant for veto purposes, rather they allow you to perform extra operations. So you should handle update action. This is example I try to fix this problem, maybe helpful with you.
def save_model(self, request, obj, form, change):
# check for update action
if obj.id is not None:
obj.save()
else: # check for create, delete action
id_li = list(EquipmentCategory.objects.values_list('id', flat=True))
if len(id_li) > 0:
max_id = max(id_li)
else:
max_id=0
obj.id = max_id+1
obj.save()
super(EquipmentCategoryAdmin, self).save_model(request, obj, form, change)
I am wondering if there is a way to override a value in the admin panel whenever a user makes a change?
Scenario:
I have a model with two fields, last_edited_by and last_edited_date.
what I would like to do is auto fill those two fields when the user goes to make changes to the entry.
Attempt:
here is some code that has allowed me to print the value of one of the fields, but there was no change in the admin panel even after applying a new value.
admin.py
class ProcessSheetRefAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, form_url='', extra_context=None):
obj = super(ProcessSheetRefAdmin, self).get_object(request, object_id)
if obj is not None:
"""
attempting to update the value in last_edited_date,
but even after applying new value, the admin panel shows old value from
last database entry.
"""
obj.last_edited_date = time.time()
if request.method == 'POST':
print "admin post"
return super(ProcessSheetRefAdmin, self).change_view(request, object_id, form_url, extra_context)
admin.site.register(ProcessSheetRefrence, ProcessSheetRefAdmin)
any help would be greatly appreciated!!
When you make change to your obj you need to save it after that.
if obj is not None:
"""
attempting to update the value in last_edited_date,
but even after applying new value, the admin panel shows old value from
last database entry.
"""
obj.last_edited_date = time.time()
obj.save()
I'm totally new in Python and Django :) and i need some help.
What i want to do:
I have a model Page and i need to add a custom field "message" when someone try to update one object.
Why? Because i'm building a revision system. This field, it's just an explanation about the change. So this field is not linked to the Page (but to another model PageRevision)
After some research i managed to add this field to my form in the admin.py file, like this:
class PageAdminForm(forms.ModelForm):
# custom field not backed by database
message = forms.CharField(required=False)
class Meta:
model = Page
it's working, my field is now displayed...But i don't want this field everywhere. Just when someone try to update a Page object.
i have found this answer different-fields-for-add-and-change-pages-in-admin but it's not working for me because it's a custom field (i think).
The rest of my code in admin.py:
class PageAdmin(admin.ModelAdmin):
form = PageAdminForm
fields = ["title", "weight", "description", "message"]
list_display = ["title", "weight", "description"]
list_filter = ["updated_at"]
def get_form(self, request, obj=None, **kwargs):
if obj is None:
# not working ?
kwargs['exclude'] = ['message']
# else:
# kwargs['exclude'] = ['message']
return super(PageAdmin, self).get_form(request, obj, **kwargs)
def save_model(self, request, obj, form, change):
if not obj.id:
obj.author = request.user
obj.modified_by = request.user
wiki_page = obj.save()
# save page in revision table
revision = PageRevision(change=change, obj=wiki_page,
request=request)
# retrieve value in the custom field
revision.message = form.cleaned_data['message']
revision.save()
def get_form doesn't exclude my custom message field because i think it doesn't know is existence. If i put another field like title, it's works.
So how to exclude the custom field from add view ?
Thanks :)
You're right, it won't work this way, because 'message' is not a field found on the Page model and the ModelAdmin class will ignore the exclusion. You can achieve this in many ways, but I think the best way to do it is this:
class PageAdmin(admin.ModelAmin):
change_form = PageAdminForm
...
def get_form(self, request, obj=None, **kwargs):
if obj is not None:
kwargs['form'] = self.change_form
return super(UserAdmin, self).get_form(request, obj, **defaults)
Basicaly here django will use an auto-generated ModelForm when adding a Page and your custom form when editing the Page. Django itself uses a similar technique to display different forms when adding and changing a User:
https://github.com/django/django/blob/stable/1.6.x/django/contrib/auth/admin.py (the interesting part is in line 68)
I just stumbled upon this same question and, since it comes in the first search results I would like to add this other solution for people that do not want to use a custom form.
You can override the get_fields method of your admin class to remove a custom field from the Add page.
def get_fields(self, request, obj=None):
fields = list(super().get_fields(request, obj=obj))
if obj is None:
fields.remove("message")
return fields
I am trying to generate a qr code to assign to an image field. I have done this with no trouble on another model, using the save_model function in ModelAdmin. Now, I need to do it in an Inline. Apparently, save_model does not work here, and I am told that save_formset is the way to go instead, however I cannot get it to work. I have compared my code to other instances of save_formset which I have seen, and cannot see any syntax errors, but django will not give me an error report so I have nothing else to go on.
class InstrumentAdmin(admin.ModelAdmin):
inlines = [
AssetInline,
]
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for f in instances:
# save the object first so we get an id number etc.
f.save()
# determine the URL
url='{}{}'.format(HOMEURL,f.get_absolute_url())
# build a qr code
qr = qrcode.QRCode(box_size=3)
qr.add_data( 'FloWave TT {} {}'.format(f,url))
qr.make(fit=True)
img=qr.make_image()
# save to memory
img_io= StringIO.StringIO()
img.save(img_io,'PNG')
img_file=InMemoryUploadedFile(img_io, None, 'assetqr{}.png'.format(f.id), 'image/png', img_io.len, None)
# update the object record with the qrcode
f.qrcode=img_file
f.save()
formset.save_m2m()
I have worked around the problem. Instead of editing the asset model directly through save_formset, I have used save_model on the parent, and used that to edit the child. Thus:
def save_model(self, request, obj, form, change):
# save the object first so we get an id number etc.
obj.save()
obj.asset.save()
# determine the URL
url='{}{}'.format(HOMEURL,obj.get_absolute_url())
# build a qr code
qr = qrcode.QRCode(box_size=3)
qr.add_data( 'FloWave TT {} {}'.format(obj,url))
qr.make(fit=True)
img=qr.make_image()
# save to memory
img_io= StringIO.StringIO()
img.save(img_io,'PNG')
img_file=InMemoryUploadedFile(img_io, None, 'qr{}.png'.format(obj.id), 'image/png', img_io.len, None)
# update the object record with the qrcode
obj.asset.qrcode=img_file
obj.asset.save()
obj.save()
Is it possible to build a two-stage form for creating an object in Django admin?
When an admin user visits /admin/my-app/article/add/, I'd like to display some options. Then, the app would display the creation page with pre-calculated fields based on the selections made.
You could overwrite the add_view method on the ModelAdmin (Source) of myapp.article. It is responsible for rendering the modelform and adding objects to the database.
While adding your functionality, you likely want to keep the original code intact instead of copying/modifying it.
Draft
def add_view(self, request, **kwargs):
if Stage1:
do_my_stuff()
return response
else:
return super(ModelAdmin, self).add_view(self, request, **kwargs)
Now you need to distinguish between the two stages. A parameter in the GET query string could do that. To pass initial data to a form in the admin, you only need to include the fieldname value pairs as parameters in the querystring.
Draft 2
def add_view(self, request, **kwargs):
if 'stage2' not in request.GET:
if request.method == 'POST':
# calculate values
parameters = 'field1=foo&field2=bar'
return redirect('myapp_article_add' + '?stage2=1&' + parameters)
else:
# prepare form for selections
return response
else:
return super(ModelAdmin, self).add_view(self, request, **kwargs)
You probably need to look at the code to return a correct response in your first stage. There are a couple of template variables add_view sets, but hopefully this is a good start to look further.