Intro: I have a post which can have multiple images I achieving this with the help of 2 models namely. Post and Prep. The post model has a user, title, a message and a post_image
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts')
title = models.CharField(max_length=250, unique=True)
message = models.TextField()
post_image = models.ImageField(upload_to='post_images/')
I am using another model to get additional images called Prep This can have multiple images.
class Prep (models.Model): #(Images)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='post_prep')
image = models.ImageField(upload_to='post_prep_images/', blank=True, null=True)
image_title = models.CharField(max_length=100)
image_description = models.CharField(max_length=250)
However unlike before each of the images from the prep model has a image_title and a image_description
The Issue: I am able to create a post successfully and also almost edit the post with one exception I cannot reduce the number of images.
If I have 3 Prep images I can add more images up to the max allowed
I can edit existing images
I cannot reduce the number of prep images
I get an error in the form
This field is required.
How do I fix this error
my post_edit view
def post_edit(request, slug):
post = get_object_or_404(Post, slug=slug)
ImageFormSet = modelformset_factory(Prep, fields=('image', 'image_title', 'image_description'), extra=7, max_num=7,
min_num=2)
if post.user != request.user:
raise Http404()
if request.method == "POST":
form = PostEditForm(request.POST or None, request.FILES or None, instance=post)
formset = ImageFormSet(request.POST or None, request.FILES or None)
if form.is_valid() and formset.is_valid():
form.save()
data = Prep.objects.filter(post=post)
for index, f in enumerate(formset):
if f.cleaned_data:
if f.cleaned_data['id'] is None:
photo = Prep(post=post, image=f.cleaned_data.get('image'), image_title=f.cleaned_data.get('image_title'),
image_description=f.cleaned_data.get('image_description'))
photo.save()
#I thought the below code will do the trick but it doesn't seem to be
elif f.cleaned_data['image'] is False or f.cleaned_data['image_title'] is False or f.cleaned_data['image_description'] is False:
photo = Prep.objects.get(id=data[index].id)
photo.image.delete()
photo.image_title.delete()
photo.image_description.delete()
photo.id.delete()
else:
photo = Prep(post=post, image=f.cleaned_data.get('image'), image_title=f.cleaned_data.get('image_title'),
image_description=f.cleaned_data.get('image_description'))
d = Prep.objects.get(id=data[index].id)
d.image=photo.image
d.image_title=photo.image_title
d.image_description=photo.image_description
d.save()
return HttpResponseRedirect(post.get_absolute_url())
else:
form = PostEditForm(instance=post)
formset = ImageFormSet(queryset=Prep.objects.filter(post=post))
context = {'form': form, 'post': post, 'formset': formset}
return render(request, 'posts/post_edit.html', context)
can_delete=True, in modelformset_factory
Related
I have several forms that take people through steps and below are the first two and the simplest ones and makes it easy to explain what i am having problem with.
The following two views are login required and contain one form on each. First view is the new_operator where the user fills out a single text input field. Second view is the new_asset where the user fills one text input field as the asset name and selects an operator from the a select/dropdown field. The question is how can i get the form to remember the operator name the user created in the previous form and make it as the default option? To be clear, i still want the user to select any other operator if they choose to do so but i want the option they just created to be the default. Thanks a lot in advance for the help.
First, here are the models:
class OperatorCompany(models.Model):
name = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='operator_added_by', null=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = "Operator Company"
verbose_name_plural = "Operator Companies"
def __str__(self):
return self.name
class AssetName(models.Model):
name = models.CharField(max_length=50, unique=True)
operator = models.ForeignKey(OperatorCompany, related_name='asset', on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='asset_added_by', null=True,
on_delete=models.SET_NULL)
class Meta:
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
views.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset')
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
and following are the forms.py without the init, clean functions and the widgets
class NewOperatorForm(forms.ModelForm):
class Meta:
model = OperatorCompany
fields = ('name',)
class NewAssetForm(forms.ModelForm):
class Meta:
model = AssetName
fields = ('name', 'operator')
To share data between multiple pages, you can use session variables. These are stored on the server and associated to clients according to the session cookie they communicate to the server at every request.
Typically, in the first view, you would add after save():
request.session['latest_created_operator_id'] = newoperator.id
to save in the session the operator id.
And in the second view, after the else,
operator_id = request.session.get('latest_created_operator_id', None)
operator = Operator.objects.filter(id=operator_id).first() # returns None if not found
form = NewAssetForm(initial={'operator': operator})
retrieves the operator and populates the form.
(That's untested code; you may need to edit a bit.)
At a glance, maybe something like this would work.
What you can do is add another URL in urls.py for new_asset which accepts a OperatorCompany id. I don't have your url config but it could be something like:
urls.py
path('wellsurfer/new_asset/<int:operator_id>', new_asset, name='wellsurfer:new_asset_operator')
view.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset', operator_id=newoperator.id)
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request, operator_id=None):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
if operator_id is not None:
operator_company = OperatorCompany.objects.get(pk=operator_id)
form.fields['operator'].initial = operator_company
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
Here I am trying to upload multiple files but it's not working properly.I got problem while storing the foreign key for each files selected ?
I got this error.
Cannot assign "<property object at 0x04667960>": "MoreImage.image_title" must be a "Gallery" instance.
models
class Gallery(models.Model):
image_title = models.CharField(max_length=100, blank=True, null=True)
image_date = models.DateField(blank=True, null=True)
image = models.ImageField(upload_to='gallery', default='default.png')
class MoreImage(models.Model):
image_title = models.ForeignKey(Gallery, on_delete=models.CASCADE)
images = models.ImageField(upload_to='moreimage', default='default.png')
date = models.DateTimeField(auto_now_add=True)
views
def add_more_image(request):
images = Gallery.objects.all().order_by('-date')
if request.method == 'POST':
form = MoreImageForm(request.POST or None, request.FILES or None)
if form.is_valid():
more = form.save(commit=False)
for file in request.FILES.getlist('image'):
MoreImage.objects.create(image_title=Gallery.pk, images=file)
#for field in request.FILES.keys():
#for form_file in request.FILES.getlist(field):
#img = MoreImage(image_title_id=Gallery.pk,images=form_file)
#img.save()
more.save()
messages.success(request, ' Images added.')
return redirect('admin:add_gallery')
MoreImage Form
class MoreImageForm(forms.ModelForm):
class Meta:
model = MoreImage
fields = '__all__'
First of all, you shouldn't call Gallery.pk because it's won't return anything since it's a class. It should be something like gallary_instance.pk
and I don't think gallary_instance.pk will work for you because you've set commit=False which prevent to save the object into DB.
Try this,
def add_more_image(request):
images = Gallery.objects.all().order_by('-date')
if request.method == 'POST':
form = MoreImageForm(request.POST or None, request.FILES or None)
if form.is_valid():
more = form.save() # remove commit=False
for file in request.FILES.getlist('image'):
MoreImage.objects.create(image_title=more.image_title, images=file)
messages.success(request, ' Images added.')
return redirect('admin:add_gallery')
I am writing a control view function edit_article to update fields of title and content of model table Article:
To update the title and content, I employed article=form.save
def edit_article(request, pk):
article = Article.objects.get(pk=pk)
if request.method == "POST":
form = ArticleForm(data=request.POST)
print(request.POST)
if form.is_valid():
article = form.save()
It reports error when issue submitting
django.db.utils.IntegrityError: (1048, "Column 'owner_id' cannot be null")
I did not change owner_id, just keep it as its previous state.
The problem is solved by explicitly re-assign attribute:
if form.is_valid():
# article = form.save()
article.title = form.cleaned_data['title']
article.content = form.cleaned_data['content']
article.save()
The model Article
class Article(models.Model):
STATUS = (
(1, 'normal'),
(0, 'deleted'),
)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
block = models.ForeignKey(Block, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField() # set the widget
status = models.IntegerField(choices=STATUS)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
class Meta:
ordering = ("id",)
def __str__(self):
return self.title
Why form.save() failed as a shortcut?
If you want to update existing record, you should pass instance object of that model to the form.
So your code would change to
if request.method == "POST":
form = ArticleForm(instance=article, data=request.POST)
...
form.valid is not working,when i fill the form the data should store in database using html form and also how to upload image,how to define the form in html page,is there anything wrong in my code,below is my models.py,views.py,forms.py is given,i have search many forums but not able to get answer
models.py
class Deal(models.Model):
Deal_Category = models.CharField(max_length=30, choices=(('F','Fashion'),
('T', 'Travel') ))
Deal_Title = models.CharField(max_length=30,blank=True)
Deal_Redemption = models.CharField(max_length=50,blank=True)
Deal_Start = models.DateField()
Deal_End =models.DateField()
Deal_Details = models.CharField(max_length=50,blank =True)
Deal_Location= models.CharField(max_length=50,blank =True)
Deal_Terms = models.CharField(max_length=50,blank =True)
#Images = models.ImageField(upload_to='static/image',blank=True)
forms.py
class DealForm(forms.ModelForm):
class Meta():
model= Deal
fields=('Deal_Category','Deal_Title','Deal_Redemption','Deal_Start','Deal_End','Deal_Details',
'Deal_Location','Deal_Terms')
views.py
def deal_form(request):
print("Deal")
if request.method == 'POST':
print("user")
form = DealForm(data=request.POST )
print("Deal1")
if form.is_valid():
print("Deal2")
form.save(commit=True)
print("Deal4")
return render(request, 'advertizer/adver_create_coupon.html')
I have a very strange problem with django forms, I display a form which includes an additional formset so that the user can also submit data for a foreign key relation at the same time.
The template always displays a form for the original model and one form for the second model.
I now want to submit the two forms without filling in anything in the second form.
On the first submission the seond form does not validate and the page is redisplayed, but on the second submission the second form is valid! Even so the POST data is identical.
How can this be possible?
Or maybe I am doing this completely wrong, how can you discern if the user did not fill in anything in the formset or if he filled in something invalid?
Here the models:
class Software(models.Model):
creation_date = models.DateTimeField(default=datetime.now)
creator = models.ForeignKey(User)
version = models.CharField(max_length=300, unique=True, editable=False)
major_version = models.IntegerField()
minor_version = models.IntegerField()
[...]
def save(self, **kwargs):
"""
This updates the version string to the combined representation.
"""
self.version = Software.combine_version_string (self.major_version, self.minor_version)
super(Software, self).save(**kwargs)
class SoftwarePatch(models.Model):
file = models.FileField(upload_to='software_patches')
file_name = models.CharField(max_length=255, editable=False)
file_date = models.DateTimeField(default=datetime.now)
upload_date = models.DateTimeField(default=datetime.now)
software = models.ForeignKey('Software', related_name='patches')
firmware_patch = models.BooleanField(default=True)
target_path = models.CharField(max_length=255, blank=True)
class Meta:
unique_together = ('software', 'file_name')
verbose_name_plural = "software patches"
def __unicode__(self):
return self.file_name
def clean(self):
if self.file and not self.file_name:
self.file_name = self.file.file.name
Here my forms:
SoftwarePatchFormSet = inlineformset_factory(Software,
SoftwarePatch,
extra=1)
class SoftwareForm(forms.ModelForm):
"""
A simple form for creating a new software.
"""
class Meta:
model = Software
And finally my view function:
def software_add(request, software_id=None):
if software_id == None:
software = Software()
else:
software = Software.objects.get(id=software_id)
if request.POST:
form = SoftwareForm(request.POST, instance=software)
if form.is_valid():
software = form.save(commit=False)
softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES, instance=software)
if softwarepatch_formset.is_valid():
software = form.save()
softwarepatch_formset.save()
# Redirect, in case of a popup close it
if request.POST.has_key("_popup"):
pk_value = software._get_pk_val()
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
# escape() calls force_unicode.
(escape(pk_value), escape(software)))
if 'next' in request.POST:
return HttpResponseRedirect(request.POST['next'])
else:
return HttpResponseRedirect(reverse('index'))
else:
form = SoftwareForm(instance=software)
softwarepatch_formset = SoftwarePatchFormSet(instance=software)
is_popup = request.GET.has_key("_popup") or request.POST.has_key("_popup")
return render_to_response(
'main/software_edit.html',
{'form': form,
'softwarepatch_formset': softwarepatch_formset,
'add': True,
'is_popup': is_popup,
},
context_instance = RequestContext(request)
)
First of all, you should set the instance argument only when creating a form / formset for an existing object i.e. one already in the DB. So for example if software_id = None and it's a GET request, you should only do form = SoftwareForm().
Also, after doing software = form.save(commit=False), you should do software.save() instead of software = form.save(). [I don't think it's really a problem though, just that you're redoing a save]. Remember that if you have a ManyToManyField in the Software model, you need to do form.save_m2m() after software = form.save() as well.
Here's what I think you should have:
def software_add(request, software_id=None):
if request.POST:
if software_id:
software = Software.objects.get(id=software_id)
form = SoftwareForm(request.POST, instance=software)
else:
form = SoftwareForm(request.POST)
if form.is_valid():
software = form.save(commit=False)
softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES, instance=software)
if softwarepatch_formset.is_valid():
software.save()
softwarepatch_formset.save()
# Redirect, in case of a popup close it
if request.POST.has_key("_popup"):
pk_value = software._get_pk_val()
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
# escape() calls force_unicode.
(escape(pk_value), escape(software)))
if 'next' in request.POST:
return HttpResponseRedirect(request.POST['next'])
else:
return HttpResponseRedirect(reverse('index'))
else:
softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES)
else:
if software_id:
software = Software.objects.get(id=software_id)
form = SoftwareForm(instance=software)
softwarepatch_formset = SoftwarePatchFormSet(instance=software)
else:
form = SoftwareForm()
softwarepatch_formset = SoftwarePatchFormSet()
is_popup = request.GET.has_key("_popup") or request.POST.has_key("_popup")
return render_to_response(
'main/software_edit.html',
{'form': form,
'softwarepatch_formset': softwarepatch_formset,
'add': True,
'is_popup': is_popup,
},
context_instance = RequestContext(request)
)
Ok I finally found my problem!
I have the following model field: file_date = models.DateTimeField(default=datetime.now)
This sets the innital-file-date to a value like this: u'2011-10-18 08:14:30.242000'
After being rendered through the html widget the value will be: u'2011-10-18 08:14:30'
So django will think the form was changed and therefore not save.
On the second load django will automatically set the truncated value as initial-file-date and then nothing is changed and the save works as expected.
So now I only have to figure out what to use instead of datetime.now. I will update this post when I have figured it out.