I have a Django 3.2, python 3.6 website. I am having issues uploading multiple files to a model that also has a M2M field. I get an error in the save_related method at the indicated line:
ValueError: "<Image: title>" needs to have a value for field "image_id" before this many-to-many relationship can be used.
I have used this same method to upload multiple files to models without an M2M field, so I am not sure where I am going wrong.
models.py
class Tags(models.Model):
tag_id = models.AutoField(primary_key=True)
tag_name = models.CharField(max_length=255)
class Image(models.Model):
image_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
description = models.TextField()
original_image = models.ImageField('original_image', upload_to=settings.ORIGINAL_IMAGE_PATH,)
exif_data = models.JSONField(default=dict)
computed_sha256 = models.CharField(editable=False, max_length=64, default="foobar")
tags = models.ManyToManyField(Tags, blank=True)
admin.py
class ImageForm(forms.ModelForm):
original_image = forms.ImageField(widget=forms.FileInput(attrs={'multiple': True}))
class ImageAdmin(admin.ModelAdmin):
form = ImageForm
class Meta:
model = Image
fields = '__all__'
def save_related(self, request, form, *args, **kwargs):
tags = form.cleaned_data.pop('tags', ())
image = form.instance
for tag in tags:
image.tags.add(tag) # error occurs here
super(ImageAdmin, self).save_related(request, form, *args, **kwargs)
def save_model(self, request, obj, form, change):
if form.is_valid():
if not change:
# Uploading one or more images))
files = request.FILES.getlist('original_image')
for f in files:
image = Image()
if "Title" not in form.cleaned_data:
form.cleaned_data['Title'] = clean_title(f.name)
image.computed_sha256 = image_processing_utils.compute_sha256(f)
image.original_image = f
image.description = form.cleaned_data['description']
image.exif_data = image_processing_utils.read_exif_data(f)
image.save()
else:
pass
I could not find a way to upload multiple files to a model with a M2M field, so I punted and took the M2M field out of the model.
models.py
class Image(models.Model):
image_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255, blank=True)
description = models.TextField()
original_image = models.FileField('original_image', upload_to=settings.ORIGINAL_IMAGE_PATH,)
exif_data = models.JSONField(default=dict)
computed_sha256 = models.CharField(editable=False, max_length=64, default="foobar")
def __str__(self):
return self.title
class Meta:
db_table = 'Image'
class ImageTags(models.Model):
image_id = models.ForeignKey(Image, on_delete=models.CASCADE)
tag_id = models.ForeignKey(Tags, on_delete=models.CASCADE)
class Meta:
db_table = 'ImageTags'
admin.py
class ImageAdminForm(forms.ModelForm):
original_image = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
def __init__(self, *args, **kwargs):
super(ImageAdminForm, self).__init__(*args, **kwargs)
tag_choices = Tags.objects.values_list('tag_id', 'tag_name')
self.fields['tags'] = forms.MultipleChoiceField(choices=tag_choices, widget=forms.SelectMultiple, required=False)
class ImageAdmin(admin.ModelAdmin):
list_display = ('image_id', 'title', 'description', 'views', 'original_image', 'get_tags', 'exif_data', 'created', 'updated')
readonly_fields = ('thumb_image', 'album_image', 'display_image', 'exif_data', 'views', )
form = ImageAdminForm
class Meta:
model = Image
fields = '__all__'
fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('title', 'description', 'original_image',)
}),
)
def get_form(self, request, obj=None, **kwargs):
# https://stackoverflow.com/questions/1057252/how-do-i-access-the-request-object-or-any-other-variable-in-a-forms-clean-met
logger.debug("get_form START")
kwargs['fields'] = flatten_fieldsets(self.fieldsets)
form = super(ImageAdmin, self).get_form(request, obj, **kwargs)
form.request_obj = request
logger.debug("get_form END")
return form
def get_fieldsets(self, request, obj=None):
logger.debug("get_fieldsets START")
import copy
fieldsets = copy.deepcopy(super(ImageAdmin, self).get_fieldsets(request, obj))
logger.debug("1 fieldsets=%s" % fieldsets)
change_page_fieldset = list(fieldsets[0][1]['fields'])
logger.debug("1 change_page_fieldset=%s" % change_page_fieldset)
#if obj:
if 'tags' not in change_page_fieldset:
change_page_fieldset.append('tags')
logger.debug('2 change_page_fieldset=%s' % change_page_fieldset)
fieldsets[0][1]['fields'] = tuple(change_page_fieldset)
logger.debug('2 fieldsets=%s' % fieldsets)
return fieldsets
def get_tags(self, obj):
tag_ids = list(ImageTags.objects.filter(image_id=obj).values_list("tag_id", flat=True))
tag_names = list(Tags.objects.filter(tag_id__in=tag_ids).values_list('tag_name', flat=True))
return ", ".join([t for t in tag_names])
def save_model(self, request, obj, form, change):
logger.debug("save_model START")
logger.debug("obj=%s, change=%s, valid=%s" % (obj, change, form.is_valid()))
logger.debug("changed fields=%s" % form.changed_data)
logger.debug("obj.original_image=%s" % obj.original_image)
if utils.is_celery_working():
if form.is_valid():
if not change:
# Uploading one or more images
logger.debug("\tvalid form")
logger.debug("form.cleaned_data=%s",form.cleaned_data)
logger.debug("files=%s" % request.FILES.getlist('original_image'))
files = request.FILES.getlist('original_image')
for f in files:
image = Image()
if not form.cleaned_data['title']:
image.title = clean_title(f.name)
else:
image.title = form.cleaned_data['title']
logger.debug("form.cleaned_data['title']=%s" % form.cleaned_data['title'])
logger.debug("f=%s" % f)
image.original_image = f
image.description = form.cleaned_data['description']
image.save()
# save the tags
tags = form.cleaned_data['tags']
for tag in tags:
ImageTags.objects.create(tag_id_id=int(tag), image_id_id=image.pk)
#super().save_model(request, obj, form, change)
else:
# processing a change form, so redo all the fields
pass
#super().save_model(request, obj, form, change)
else:
# error - form is invalid
pass
else:
# error - celery not working
pass
logger.debug("save_model END")
Related
I'm having trouble with the admin panel. The problem arose due to the presence of relationships model 'ElementVersion'. I don’t know how to display the fields of related models in the admin panel through ElementVersion.
My models.py file
class Refbook(models.Model):
code = models.CharField(max_length=100, unique=True, verbose_name='refbook code')
name = models.CharField(max_length=300, verbose_name='refbook name')
description = models.TextField()
def __str__(self):
return self.name
class Version(models.Model):
refbook_id = models.ForeignKey('Refbook',
on_delete=models.CASCADE,
related_name='versions',
verbose_name='refbook name')
version = models.CharField(max_length=50, verbose_name='refbook version')
date = models.DateField(verbose_name='start date')
def __str__(self):
return self.version
class Meta:
unique_together = [['refbook_id', 'version'], ['refbook_id', 'date']]
class Element(models.Model):
version_id = models.ManyToManyField('Version',
through='ElementVersion',
verbose_name='id_version')
code = models.CharField(max_length=100, unique=True, verbose_name='element's code')
value = models.CharField(max_length=300, verbose_name='element's value')
def __str__(self):
return self.code
class ElementVersion(models.Model):
version = models.ForeignKey(Version,
on_delete=models.CASCADE,
verbose_name='version')
element = models.ForeignKey(Element,
on_delete=models.CASCADE,
verbose_name='element')
class Meta:
unique_together = ('version', 'element')
This is the admin.py file
from .models import Refbook, Version, Element, ElementVersion
class VersionInline(admin.StackedInline):
model = Version
fields = ['date']
readonly_fields = ('date',)
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
#admin.register(Refbook)
class RefbookAdmin(admin.ModelAdmin):
list_display = ['id', 'code', 'name', 'get_last_version', 'get_date_version']
inlines = [VersionInline]
def get_last_version(self, obj):
return obj.versions.values_list('version').last()
get_last_version.short_description = "last version"
def get_date_version(self, obj):
return obj.versions.values_list('date').last()
get_date_version.short_description = "start date"
class ElementVersionInline(admin.TabularInline):
model = ElementVersion
extra = 0
#admin.register(Version)
class VersionAdmin(admin.ModelAdmin):
list_display = ['get_code_refbook', 'refbook_id', 'version', 'date']
inlines = [ElementVersionInline]
def get_code_refbook(self, obj):
return obj.refbook_id.code
get_code_refbook.short_description = 'refbook code'
#admin.register(Element)
class ElementAdmin(admin.ModelAdmin):
list_display = ['id', 'code', 'value']
inlines = [ElementVersionInline]
I want to solve two problems:
on the version editing page, you need the ability to fill in the elements in this version. At the same time, I want all fields with readable names to be displayed, that is, the code and value
on the element editing page, you need the ability to select "reference-version" from the double list
I want to obtain the data of a field that is in an intermediate table of a many-to-many relationship, the field I want is quantity, it is in PecosaMaterial
this is my code:
models.py
class Material(models.Model):
nombre_material = models.CharField(max_length=200, verbose_name="Nombre", null=True)
class Meta:
verbose_name = "Material"
verbose_name_plural = "Materiales"
def __str__(self):
texto_material = "{0} | {1}"
return texto_material.format(self.codigo_material, self.nombre_material, self.precio_unitario)
class Pecosa(models.Model):
matpecosa = models.ManyToManyField(Material, through="PecosaMaterial")
descripcion_pecosa = models.TextField(verbose_name="Justificación")
class Meta:
verbose_name = "pecosa"
verbose_name_plural = "pecosas"
def __str__(self):
return self.descripcion_pecosa
class PecosaMaterial(models.Model):
pecosa = models.ForeignKey(Pecosa, on_delete=models.CASCADE)
material = models.ForeignKey(Material, on_delete=models.CASCADE)
cantidad = models.IntegerField(verbose_name="Cantidad")
precio_total_material = models.DecimalField(max_digits=12, decimal_places=6, verbose_name="Precio total del material", null=True, blank=True)
def __unicode__(self):
return self.pecosa
admin.py:
class PecosaAdmin(ImportExportModelAdmin,admin.ModelAdmin):
inlines = [PecosaMaterialInline,]
list_per_page = 15
exclude = ('precio_total_pecosa',)
resource_class = PecosaAdminResource
# Solucionar el problema de las busquedas
# search_fields = ('solicitante',)
readonly_fields = ('creacion_pecosa', 'modificacion_pecosa')
list_display = ('descripcion_pecosa', 'cantidad', 'materiales', 'precio_unitario', 'precio_total_pecosa', 'solicitante', 'creacion_pecosa')
from .models import PecosaMaterial
def cantidad(self, obj):
cantidad = PecosaMaterial.objects.all()
return cantidad
def precio_unitario(self, obj):
return format_html("</br>".join([str(p.precio_unitario) for p in obj.matpecosa.all()]))
def materiales(self, obj):
return format_html("</br>".join([m.nombre_material for m in obj.matpecosa.all()]))
ordering = ('creacion_pecosa',)
list_filter = ('creacion_pecosa',)
As you can see with the cantidad function, I tried to obtain the data but it did not work, sorry for the bad code and thanks in advance
from .models import PecosaMaterial
def cantidad(self, obj):
cantidad = PecosaMaterial.objects.all()
return cantidad
I'm trying to display a related field in the admin section but I'm getting the following error:
'NoneType' object has no attribute 'ref_code'
models.py
class Orden(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
producto = models.ManyToManyField(
ProductosOrden)
medio_de_venta = models.ForeignKey(MediosVenta, null=True, blank=False, on_delete=models.SET_NULL)
fecha_venta = models.DateField(blank=False, null=False)
ordenado = models.BooleanField(default=False)
ref_code = models.CharField(max_length=20, blank=True, null=True)
promo = models.ForeignKey(Promos,null=True, blank=True, on_delete=CASCADE )
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
vendido_por = models.CharField(max_length=20, null=False, blank=False)
class Meta:
verbose_name_plural = "Ordenes"
ordering = ["-id"]
def __str__(self):
return self.user.username
class Cliente(models.Model):
nombre = models.CharField(max_length=60, null=False, blank=False)
email = models.EmailField(blank=False, null=False)
email_valido = models.BooleanField(null=True, blank= True, verbose_name='Email Válido')
telefono = models.CharField(
max_length=17, null=True, blank=True, verbose_name='Teléfono')
lead = models.ForeignKey(Leads, models.SET_NULL, null=True, blank=True)
activo = models.BooleanField(default=False)
orden = models.ForeignKey(Orden, null=True, blank=True, on_delete=CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=20, null=False, blank=False)
class Meta:
verbose_name_plural = "Clientes"
ordering = ["-id"]
def __str__(self):
return self.nombre
admin.py
class ClienteAdmin(admin.ModelAdmin):
list_display = ('id', 'nombre', 'activo', 'get_ref_code')
readonly_fields=('id',)
search_fields = ['nombre']
actions = ['activa','desactivar']
#admin.action(description='Desactivar')
def desactivar(self, request, queryset):
queryset.update(activo = False)
#admin.action(description='Activar')
def desactivar(self, request, queryset):
queryset.update(activo = True)
#admin.display(description= 'ref_code')
def get_ref_code(self, obj):
if not obj.orden.ref_code:
return None
else:
return obj.orden.ref_code
If I just use the return as:
return obj.orden
results are displayed with the name, but I want to display the ref_code results which is under the Orden model and it is not working.
correct if condition because orden column is nullable in Cliente model.
#admin.display(description= 'ref_code')
def get_ref_code(self, obj):
if not obj.orden_id:
return None
else:
return obj.orden.ref_code
I need some help to prepopulate user's email in my models. There has to be an easy way, but i think i am too tired to see the trick. Basically what i need, after the user has been logged in to admin, i need those fields like user, email and name from the User model to be already populated with the info of the current user.
Your help will be much appreciated!
Currently my models are like this:
class ParcareManager(models.Manager):
def active(self, *args, **kwargs):
return super(ParcareManager, self).filter(draft=False).filter(parking_on__lte=timezone.now())
class Parcare(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, default=1, on_delete=True)
email=models.EmailField(blank=True, null=True)
# email = models.EmailField(settings.AUTH_USER_MODEL)
parking_on = models.DateField(auto_now=False, auto_now_add=False,blank=True, null=True)
parking_off = models.DateField(
auto_now=False, auto_now_add=False, blank=True, null=True) #plecarea din parcare
location =models.CharField(max_length=120, default="P1", blank=True, null=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False,blank=True, null=True)
timestamp=models.DateTimeField(auto_now=False, auto_now_add=True,blank=True, null=True)
venire = models.TimeField(default=datetime.time(9, 00),auto_now=False, auto_now_add=False )
plecare = models.TimeField(default=datetime.time(
18, 00), auto_now=False, auto_now_add=False)
objects = ParcareManager()
def __str__(self):
return self.email + " | " + self.location + " | " + str(self.parking_on) + " | " + str(self.parking_off)
class Meta:
verbose_name_plural = "parcare"
class Meta:
ordering = ["-timestamp", "-updated"]
My admin is like this:
from django.contrib import admin
# Register your models here.
from .models import Parcare
class ParcareModelAdmin(admin.ModelAdmin):
list_display = [ "email","user", "location",
"parking_on", "parking_off", "venire", "plecare"]
list_display_links = ["email", "user" ]
list_editable = [ "parking_off", "parking_on","venire", "plecare"]
list_filter = ["parking_on", "parking_off", "venire", "plecare"]
search_fields = ["location", "name"]
class Meta:
model = Parcare
def email(self, obj):
return obj.user.email
admin.site.register(Parcare, ParcareModelAdmin)
If this is the case, then you may try this:
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if not obj:
user = request.user
form.base_fields['user'].initial = user
form.base_fields['email'].initial = user.email
return form
Here you are fetching the logged in user object from request and then initializing the values from that user.
So I have a join model defined as follow:
class EventTrack(models.Model):
dj = models.ForeignKey(DjProfile, blank=True)
track = models.ForeignKey(Track, blank=True)
event = models.ForeignKey(Event, blank=True)
def __str__(self):
return '%s - %s' % (self.event, self.track)
Is there a way I can use django-autocomplete-light with this model?
I know how to use it with inline models, but I don't get how to use it with standard field (in this case they are fk though).
At the moment I have the follow, which does not include the autocomplete functionality:
class EventTrackAdmin(admin.ModelAdmin):
fields = ['event', 'dj', 'track']
list_display = ('event', 'dj', 'track')
search_fields = ['event', 'dj', 'track']
admin.site.register(EventTrack, EventTrackAdmin)
edit:
I defined a DjForm as follow:
class DjForm(ModelForm):
dj = ModelChoiceField(
queryset=DjProfile.objects.all(),
widget=autocomplete.ModelSelect2(url='dj-autocomplete')
)
class Meta:
model = DjProfile
fields = '__all__'
Views:
#method_decorator(login_required, name='dispatch')
class DjProfileAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated():
return DjProfile.objects.none()
qs = DjProfile.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
And routing:
from frontend.views import DjProfileAutocomplete
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^dj-autocomplete/$', DjProfileAutocomplete.as_view(create_field='name'), name='dj-autocomplete'),
]
Everything works fine, and if I browse the endpoint I get the json with Djs result. My only problem is to use this in the EventTrack Model, in the admin.
sooo, actually it was easy:
forms.py
class EventTrackForm(ModelForm):
dj = ModelChoiceField(
queryset=DjProfile.objects.all(),
widget=autocomplete.ModelSelect2(url='dj-autocomplete')
)
class Meta:
model = EventTrack
fields = '__all__'
admin.py
class EventTrackAdmin(admin.ModelAdmin):
form = EventTrackForm
...