Django modelformset_factory to update the existing images - django

class ProductImageForm(forms.ModelForm):
class Meta:
model = ProductImage
fields = ['product', 'original', 'caption']
# use ImageInput widget to create HTML displaying the
# actual uploaded image and providing the upload dialog
# when clicking on the actual image.
widgets = {
'original': ImageInput(),
}
def save(self, *args, **kwargs):
# We infer the display order of the image based on the order of the
# image fields within the formset.
kwargs['commit'] = False
obj = super(ProductImageForm, self).save(*args, **kwargs)
obj.display_order = self.get_display_order()
obj.save()
return obj
def get_display_order(self):
if self.prefix:
return self.prefix.split('-').pop()
else:
return 1
I am using django modelformset_factory to insert images into database and also update the images.The problem now is that image is inserted into database instead of updation while using modelformset_factory.Following below are the my code.Please help me to update other than saving
ImageFormSet = modelformset_factory(ProductImage,
form=ProductImageForm, extra=0, can_delete=True)
formset = ImageFormSet(self.request.POST or None, self.request.FILES or None, data)
if formset.is_valid():
try:
instances = formset.save(commit=False)
for instance in instances:
instance.save()
except Exception as e:
pass
else:
err = formset.errors
raise error_message(err)

Related

How can I render a Django Formset with some data for update process?

I am using Dajngo formset with inlineformset_factory
What I need it when the user click in the update like It should render formset with the value.
This is the code I did but nothing works:
This is how I create a formset
OrderItemFormset = inlineformset_factory(
Order, OrderItem, fields='__all__', extra=1, can_delete=False)
And here how I tried to render the formset with the queryset=my_query_set.
if 'id' in kwargs.keys():
order = Order.objects.get(id=kwargs.get('id'))
order_items = OrderItem.objects.filter(order_id=kwargs.get('id'))
else:
order = None
order_items = None
order_form = OrderForm(instance=order)
print(order_items)
order_item_form = OrderItemFormset(queryset=order_items)
When I click in the update link Django render the Parent form with the data I need, but this does not work with formset it just give me one row with empty data.
After some trying to solve my problem I found that I use queryset, and I think I should use instance instead.
And the other thing is that the instance was from OrderItem model and instead it should be from the Order model so the code should look like this.
def get(self, request, *args, **kwargs):
if 'id' in kwargs.keys():
order = Order.objects.get(id=kwargs.get('id'))
else:
order = None
order_form = OrderForm(instance=order)
order_item_form = OrderItemFormset(instance=order)
orders = Order.objects.all()
context = {'order_form': order_form,
'order_item_form': order_item_form, 'orders': orders}
return render(request, self.template_name, context)

How to initialize a Django ModelChoiceField from a view?

I have the following model relationship where a task is associated to an objective and an objective is associated to a User. I created a Django form that displays all the objectives that are associated to a User.
class DropDownMenuSelectedObjectivesForm(forms.Form):
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('id')
super(DropDownMenuSelectedObjectivesForm, self).__init__(*args, **kwargs)
self.fields['objective'] = forms.ModelChoiceField(queryset = Objective.objects.values_list('objective',flat=True)
.filter(accounts=User.objects.get(id=user_id),
status='In Progress'), empty_label=None)
When I open my views.py I am able to see all the objectives from the User but I would like that the Django form dropdown menu could be initialized with the current objective that is associated with a task.
So far, I have tried the following to initialize the dropdown menu but I've got no success.
# views.py
def update_task(request, id):
'''Update a task'''
task = Task.objects.get(pk=id) # get the task id from the db
associated_task_objective = task.objective.values('objective')[0]
form = TaskModelForm(request.POST or None, instance=task)
# Attempt 1 to initialize the ModelChoiceField
objective = DropDownMenuSelectedObjectiveForm(id = request.user.id, initial = { 'objective': associated_task_objective})
if request.method == "GET":
template_name = 'task/formTask.html'
return render(request, template_name, {'form': form, 'objective':objective})
# forms.py
class DropDownMenuSelectedGoalsForm(forms.Form):
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('id')
super(DropDownMenuSelectedGoalsForm, self).__init__(*args, **kwargs)
# Attempt 2 to initialize the ModelChoiceField
self.fields['objective'] = forms.ModelChoiceField(queryset = Objective.objects.values_list('objective',flat=True)
.filter(accounts=User.objects.get(id=user_id),
status='In Progress'), empty_label=None, initial=2)
Even if I try to initialize the ModelChoiceField from the forms.py with a valid pk number, the modelchoicefield doesn't initialize.
Any idea or suggestion is really appreciated :)
I had to do the following to solve this problem:
def update_task(request, id):
'''Update a task'''
task = Task.objects.get(pk=id) # get the task id from the db
associated_task_objective = task.goal.values_list('goal',flat=True)[0]
form = TaskModelForm(request.POST or None, instance=task)
objective = DropDownMenuSelectedObjectiveForm(id = request.user.id, initial = { 'objective': associated_task_objective})
The initial keyword is looking by name of the objective, not by pk.

Creating UpdateForm by using ModelForm in Django

I have a Django application and I wanna create an update form with ModelForm but I have to get the fields parameter from user_profile model. So I have created my form and view such as below.
forms.py;
class UpdateForm(forms.ModelForm):
class Meta:
model = Data
fields = []
def __init__(self, *args, **kwargs):
input_choices = kwargs.pop('input_choices')
super(UpdateForm, self).__init__(*args,**kwargs)
self.fields = input_choices
views.py;
#login_required(login_url = "user:login")
def updateData(request,id):
features = request.user.profile.selected_features
instance = get_object_or_404(Data,id = id)
form = UpdateForm(request.POST or None, input_choices=features, instance=instance)
if form.is_valid():
data = form.save(commit=False)
data.author = request.user
data.save()
return redirect("data:dashboard")
return render(request,"update.html",{"form":form})
However, I'm getting a list indices must be integers or slices, not str error in the return render(request,"update.html",{"form":form}) line. There is an issue with the fields parameter but I couldn't find anything.
How can I pass the fields parameter from view to ModelForm?
You can use modelform_factory to create a form with dynamic fields
from django.forms import modelform_factory
#login_required(login_url = "user:login")
def updateData(request,id):
features = request.user.profile.selected_features
instance = get_object_or_404(Data, id=id)
UpdateForm = modelform_factory(Data, fields=features)
form = UpdateForm(request.POST or None, instance=instance)
If features is a queryset, you'll probably have to use something like features.values_list('name', flat=True) to get just the names of the fields

two copies of images are stored each time I upload an image in django?

when I upload an image, i see it uploaded twice in my project. The two locations are
/Users/myproject/media/ and /Users/myproject/media/assets/uploaded_files/username/. I expect the image to be uploaded only to the latter. why two copies are uploaded and how to avoid it?
In settings.py:
MEDIA_URL="/media/"
MEDIA_ROOT = '/Users/myproject/media/'
Here is models.py
UPLOAD_FILE_PATTERN="assets/uploaded_files/%s/%s_%s"
def get_upload_file_name(instance, filename):
date_str=datetime.now().strftime("%Y/%m/%d").replace('/','_')
return UPLOAD_FILE_PATTERN % (instance.user.username,date_str,filename)
class Item(models.Model):
user=models.ForeignKey(User)
price=models.DecimalField(max_digits=8,decimal_places=2)
image=models.ImageField(upload_to=get_upload_file_name, blank=True)
description=models.TextField(blank=True)
EDIT:
I am using formwizards. Here is the views.py:
class MyWizard(SessionWizardView):
template_name = "wizard_form.html"
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT))
#if you are uploading files you need to set FileSystemStorage
def done(self, form_list, **kwargs):
for form in form_list:
print form.initial
if not self.request.user.is_authenticated():
raise Http404
id = form_list[0].cleaned_data['id']
try:
item = Item.objects.get(pk=id)
print item
instance = item
except:
item = None
instance = None
if item and item.user != self.request.user:
print "about to raise 404"
raise Http404
if not item:
instance = Item()
for form in form_list:
for field, value in form.cleaned_data.iteritems():
setattr(instance, field, value)
instance.user = self.request.user
instance.save()
return render_to_response('wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list], })
def edit_wizard(request, id):
#get the object
item = get_object_or_404(Item, pk=id)
#make sure the item belongs to the user
if item.user != request.user:
raise HttpResponseForbidden()
else:
#get the initial data to include in the form
initial = {'0': {'id': item.id,
'price': item.price,
#make sure you list every field from your form definition here to include it later in the initial_dict
},
'1': {'image': item.image,
},
'2': {'description': item.description,
},
}
print initial
form = MyWizard.as_view([FirstForm, SecondForm, ThirdForm], initial_dict=initial)
return form(context=RequestContext(request), request=request)
According to the docs, you need to clean up the temporary images yourself, which is what's happening to you.
Here's an issue that was just merged into master and backported. You can try calling storage.reset after finishing all of the successful processing.

Django - MultipleCheckBoxSelector with m2m field - How to add object instead of save_m2m()

I use inlineformset_factory with a custom form option in order to change the queryset and the widget of a m2m field, ie: ezMap. I want the form to give the user the option to add or remove the current selected_map to the m2m field with CheckBoxSelectMultiple widget. However, I dont want to give the user the ability to remove other objects that were already there. The problem is when I save the formset with formset.save_m2m(), it overides the field and erase all objects that were already saved.
How could I just add a new object without erasing others?
models: (some of unecessary fields were removed)
class Shapefile(models.Model):
filename = models.CharField(max_length=255)
class EzMap(models.Model):
map_name = models.SlugField(max_length=50)
layers = models.ManyToManyField(Shapefile, verbose_name='Layers to display', null=True, blank=True)
class LayerStyle(models.Model):
styleName = models.SlugField(max_length=50)
layer = models.ForeignKey(Shapefile)
ezMap = models.ManyToManyField(EzMap)
forms:
class polygonLayerStyleFormset(forms.ModelForm):
add_to_map = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleFormset, self).__init__(*args, **kwargs)
self.fields['conditionStyle'].help_text = "Put * if you want to select the entire table"
self.fields['ezMap'].widget = forms.CheckboxSelectMultiple()
self.fields['ezMap'].queryset = EzMap.objects.filter(id=self.map_selected.id)
self.fields['ezMap'].help_text =""
class Meta:
model = LayerStyle
def save(self, *args, **kwargs):
instance = super(polygonLayerStyleFormset, self).save(*args, **kwargs)
instance.add_to_map = self.cleaned_data['add_to_map']
return instance
ftlStylePolygonFormset = inlineformset_factory(Shapefile, LayerStyle, can_delete=True, extra=1, max_num=5,
fields = ['styleName', 'conditionStyle', 'fillColor', 'fillOpacity', 'strokeColor', 'strokeWeight', 'ezMap'], form=polygonLayerStyleFormset)
views:
def setLayerStyle(request, map_name, layer_id):
map_selected = EzMap.objects.get(map_name=map_name, created_by=request.user)
layer_selected = Shapefile.objects.get(id=layer_id)
layerStyle_selected = LayerStyle.objects.filter(layer=layer_selected)
styleFormset = ftlStylePolygonFormset
if request.POST:
formset = styleFormset(request.POST, instance=layer_selected)
if formset.is_valid():
instances = formset.save()
for instance in instances:
if instance.add_to_map:
instance.ezMap.add(map_selecte)
else:
instance.ezMap.remove(map_selected)
save_link = u"/ezmapping/map/%s" % (map_name)
return HttpResponseRedirect(save_link)
else:
formset = styleFormset(instance=layer_selected)
#set initial data for add_to_map
for form in formset:
if form.instance.pk:
if map_selected in form.instance.ezMap.all():
form.fields['add_to_map'].initial = {'add_to_map': True}
I am confused as to what you're doing with the ezMap form field. You set its queryset to a single-element list, then use a CheckboxSelectMultiple widget for it. Are you setting up to let the user deselect that matching map, but not add new ones?
To do this at initialization, you need to define a custom base formset class and pass that in as the formset argument to your factory.
from django.forms.models import BaseInlineFormSet
class polygonLayerStyleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleForm, self).__init__(*args, **kwargs)
self.fields['conditionStyle'].help_text = "Put * if you want to select the entire table"
self.fields['ezMap'].widget = forms.CheckboxSelectMultiple()
self.fields['ezMap'].queryset = EzMap.objects.filter(id=self.map_selected.id)
self.fields['ezMap'].help_text =""
class Meta:
model = LayerStyle
class polygonLayerStyleFormset(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.map_selected = kwargs.pop("map_selected", None)
super(polygonLayerStyleFormset, self).__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['map_selected'] = self.map_selected
return super(polygonLayerStyleFormset, self)._construct_form(i, **kwargs)
ftlStylePolygonFormset = inlineformset_factory(Shapefile, LayerStyle, formset=polygonLayerStyleFormset, form=polygonLaterStyleForm, # and other arguments as above
)
It might be simpler to just go through the formset forms and directly change the field's queryset after creating it in your view:
formset = ftlStylePolygonFormset(instance=layer_selected)
for form in formset.forms:
form.fields['ezMap'].queryset = EzMap.objects.filter(id=map_selected.id)
Speaking of which, the usual convention is to split the POST and GET cases in the view:
from django.shortcuts import render
def setLayerStyle(request, map_name, layer_id):
map_selected = EzMap.objects.get(map_name=map_name, created_by=request.user)
layer_selected = Shapefile.objects.get(id=layer_id)
layerStyle_selected = LayerStyle.objects.filter(layer=layer_selected)
if request.method == 'POST':
formset = ftlStylePolygonFormset(request.POST, instance=layer_selected, map_selected=map_selected)
if formset.is_valid():
instances = formset.save()
save_link = u"/ezmapping/map/%s" % (map_name)
return HttpResponseRedirect(save_link)
else:
formset = ftlStylePolygonFormset(instance=layer_selected, map_selected=map_selected)
return render(request, "ezmapping/manage_layerStyle.html", {'layer_style': layerStyle_selected, 'layerStyleformset': formset, 'layer': layer_selected})
And it's better to use the redirect shortcut to reverse lookup a view for your redirect on success rather than hardcode the target URL. And to use get_object_or_404 or some equivalent when accessing objects based on URL arguments - right now a bogus URL will trigger an exception and give the user a 500 status error, which is undesirable.
To conditionally add to the ezMap relationship:
class polygonLayerStyleForm(forms.ModelForm):
add_to_map = forms.BooleanField()
def save(self, *args, **kwargs):
instance = super(polygonLayerStyleForm, self).save(*args, **kwargs)
instance.add_to_map = self.cleaned_data['add_to-map']
return instance
Then in the view:
instances = formset.save()
for instance in instances:
if instance.add_to_map:
instance.ezMap.add(map_selected)
You could also do the add call in the save method, but then you'd have to set the map as member data sometime previously - and more importantly, deal with the commit=False case.