save() generating new entry instead updating Django - python

I am not understanding why, but an instance always get created copying the one I am trying to edit. Also, as I can see FormSet I am using does not have an "instance" parameter to be added to its constructor. Anyways, the problem is that an instance of both Offer and OfferItem gets generated when I am editing an object.
def manage_offer(request, number=None):
param_offer = Offer.objects.filter(id=number).first()
param_items = OfferItem.objects.filter(offer=param_offer).values()
if request.method == 'POST':
offer_form = OfferForm(request.POST, instance=param_offer)
item_formset = OfferItemFormSet(request.POST, initial=param_items)
if offer_form.is_valid() and item_formset.is_valid():
# User selected go back and correct something
if request.POST.get('back', False):
return render(request,
'offer_edit.html',
{
'forms': offer_form,
'formset': item_formset,
'offer_edit': True,
})
# Proceeds with either saving or submitting request
offer = offer_form.save(commit=False)
offer.tax = offer_form.cleaned_data['tax'].value
#Sotres items to be sent back to commit part
offer_items = []
#Gets the items from the form and stores them to conf. page
for item_in_formset in item_formset.forms:
item = item_in_formset.save(commit=False)
item.item_code = get_item_code(item_in_formset.cleaned_data['name'])
item.type = get_item_type(item_in_formset.cleaned_data['name'])
offer.update_total(item.calc_total())
# Adds items into list for invoice_ready page
offer_items.append(item)
# Request goes to confirmation page
if request.POST.get('proceed', False):
return render(request,
'offer_edit.html',
{
'offer_form': offer_form,
'item_formset': item_formset,
'offer_ready': True,
'offer': offer,
'items': offer_items,
})
# Passes confirmation page and saves offer
offer.save()
# Makes sure the value is correct by recalculating
offer.reset_total()
for obj_item in offer_items:
obj_item.offer = offer
offer.update_total(obj_item.calc_total())
#commits to DB
offer.save()
obj_item.save()
return render(request,
'offer_edit.html',
{
'success_add_offer': True,
'offer': offer,
},
)
# GET generates a blank or instanced page
else:
offer_form = OfferForm(initial=
{'company': Company.objects.filter(is_default=True).first(),
'tax': Tax.objects.filter(is_default=True).first()
}, instance=param_offer)
item_formset = OfferItemFormSet(initial=param_items)
return render(request, 'offer_edit.html',
{
'forms': offer_form,
'formset': item_formset,
'edit_offer': number,
})
Forms.py
class OfferItemForm(ModelForm):
class Meta:
model = OfferItem
# Some widgets and stuff ...
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
OfferItemFormSet = formset_factory(OfferItemForm, formset=RequiredFormSet)

I found the problem: since I am using the same View function to either edit or add a new entry, on my template form I must make sure I am also identifying if I am editing or not, because the function takes a parameter in case I am editing. In that case I must change the URL in the Post form.
Just a silly mistake take took me a few hours to find out.

Related

How to add URL parameters to Wagtail archive page with filter form?

This is about an archive page in Wagtail CMS where a large number of posts can be filtered by certain properties. The filtering is handled by a form with a few select elements. The form is validated in the serve() method of the page. The queryset is filtered there accordingly as well. Here is some exemplary code to illustrate what I mean:
from .forms import ArchivePageFilterForm
...
class ArchivePage(Page):
...
def pages(self):
return SomeModel.objects.live() # exemplary queryset
def serve(self, request):
filter_form = ArchivePageFilterForm(request.POST or None)
if request.method == "POST":
if filter_form.is_valid():
name = filter_form.cleaned_data["name"]
category = filter_form.cleaned_data["category"]
color = filter_form.cleaned_data["color"]
whatever = filter_form.cleaned_data["whatever"]
selected_pages = filter_query(
self.pages, name, category, color, whatever
) # imagine some filtering magic here
return render(request, "pages/archive_page.html", {
"page": self,
"pages": selected_pages,
"form": ArchivePageFilterForm(initial={
"name": name,
"category": category,
"color": color,
"whatever": whatever,
}),
})
# fallback
return render(request, "pages/archive_page.html", {
"page": self,
"pages": self.pages,
"form": ArchivePageFilterForm(),
})
The RoutablePageMixin did not work for me here, because it only works with fixed URL schemes. I wonder how I can add URL parameters only as needed, one by one, depending on the active filters. Exemplary URLs that would be possible with the same page:
.../archive/?name=janedoe
.../archive/?name=janedoe&category=essay
.../archive/?category=essay
.../archive/?category=essay&color=green&whatever=foobar
... you get the idea. The basic concept of URL parameters. How can I add them in Wagtail via the render method? Or how can I get them to work with RoutablePageMixin in a non-fixed way with validating a form at the same time?

How to fix a Django view that is not returning an HttpResponse Object? (CS50 Project 1)

I am receiving the following error when submitting a form.
ValueError at /edit_entry/hi/
The view encyclopedia.views.edit_entry didn't return an HttpResponse object. It returned None instead.
Here is the views.py that is triggering the error.
def edit_entry(request, title):
if request.method == "POST":
form = NewEditEntryForm(request.POST)
if form.is_valid():
title = form.cleaned_data["title"]
content = form.cleaned_data["content"]
util.save_entry(title, content)
return HttpResponseRedirect("/wiki/" + title)
else:
form = NewEditEntryForm()
return render(request, "encyclopedia/edit_entry.html",{
"form": NewEditEntryForm(),
"title": title,
"content": util.get_entry(title)
})
What is the issue and how can I fix it?
(I also need help prepopulating the form with already existing data. I have tried using initial, but that has not worked. What is the best way to prepopulate the form with existing data?)
util.save_entry
def save_entry(title, content):
"""
Saves an encyclopedia entry, given its title and Markdown
content. If an existing entry with the same title already exists,
it is replaced.
"""
filename = f"entries/{title}.md"
if default_storage.exists(filename):
default_storage.delete(filename)
default_storage.save(filename, ContentFile(content))
sorry, I thought that you have a model.
# on util.py
def get_entry_content(title):
filename = f"entries/{title}.md"
return default_storage.open(filename).read()
# on views.py
def edit_entry(request, title):
if request.method == "POST":
form = NewEditEntryForm(request.POST)
if form.is_valid():
title = form.cleaned_data["title"]
content = form.cleaned_data["content"]
util.save_entry(title, content)
return HttpResponseRedirect("/wiki/" + instance.title)
else:
content = util.get_entry_content(title)
initial_dict = {
"title" : title,
"content" : content,
}
form = NewEditEntryForm(initial=initial_dict)
return render(request, "encyclopedia/edit_entry.html", {
"form": form,
})
All right, I think if this is not doing what you want, i would test the save_entry function in the console, creating and updating to see if it works or not.

Django form is being validated before it is submitted [duplicate]

This question already has an answer here:
Django: form validation errors on form creation
(1 answer)
Closed 3 years ago.
I have a custom bootstrap form which is displayed in the Dashboard. The problem is, that when the user goes to the dashboard, he sees validation errors right away, even though he did not submit the form yet (the text field should be required).
I do not understand why this is happening, any help is appreciated! :)
Picture of the problem (I do not want the red "The field is required" message to display now, only after submitting):
Form:
class MinitaskForm(forms.ModelForm):
class Meta:
model = Minitask
fields = ()
def __init__(
self,
data=None,
files=None,
auto_id="id_%s",
prefix=None,
initial=None,
error_class=ErrorList,
label_suffix=None,
empty_permitted=False,
instance=None,
use_required_attribute=None,
minitask=None,
):
super().__init__(
data,
files,
auto_id,
prefix,
initial,
error_class,
label_suffix,
empty_permitted,
instance,
use_required_attribute,
)
assert minitask is not None
self.fields["selected_choice"] = forms.CharField(
widget=forms.RadioSelect(
choices=[(val, val) for val in minitask.choices]
),
required=False,
label="Which of these emotions best describes your day?",
)
self.fields["reason"] = forms.CharField(label="Why?")
Views:
#login_required(login_url="login")
def dashboard(request):
task = request.user.current_weekly_task()
user_id = request.user.id
solutions = Solution.objects.filter(user_id=user_id)
minitask = request.user.current_minitask()
minitasks = request.user.minitask_set.filter(selected_choice__isnull=False)
if request.method == "POST":
if "minitask" in request.POST:
form = MinitaskForm(request.POST, minitask=minitask)
if "selected_choice" not in request.POST:
form.add_error("selected_choice", "Can't be blank")
messages.error(request, "You must pick your today's emotion")
if form.is_valid():
minitask.reason = request.POST["reason"]
minitask.selected_choice = request.POST["selected_choice"]
minitask.user = request.user
minitask.save()
messages.success(request, "Your daily status has been updated.")
return redirect("dashboard")
else:
mini_form = MinitaskForm(
minitask=minitask,
data={
"reason": minitask.reason,
"selected_choice": minitask.selected_choice,
},
)
return render(
request,
"app/dashboard.html",
{
"solutions": solutions,
"task": task,
"mini_form": mini_form,
"minitask": minitask,
"minitasks": minitasks,
"user": request.user,
},
)
Template:
<form method="POST" class="post-form">
{% csrf_token %}
{% bootstrap_form mini_form %}
<button
type="Submit"
id="minitask-button"
class="save btn btn-pink"
name="minitask"
>Save minitask</button>
</form>
In your views:
if request.method == "POST":
...
else:
mini_form = MinitaskForm(
minitask=minitask,
data={
"reason": minitask.reason,
"selected_choice": minitask.selected_choice,
},
)
According to the docs:
A Form instance is either bound to a set of data, or unbound.
Most of the times, a form gets its data from the user through a POST request. (bound form)
The GET request provides the user with the form in order to fill it with data. (unbound form)
Therefore, through a GET request, you need to provide the user with an unbound form.
In your code, you declare that if the request is not POST (a GET request is not POST), then return a bound form populated with data you programmatically provide.
This does not make sense.
Chances are that if you insert a breakpoint() after else, render the page with ./manage.py runserver and type in the prompt provided in the console:
minitask.reason == None the result will be True.
The above, mean that you bound your form with data that contain an empty reason which is not allowed.
If you want to provide initial data in your unbound form, you can do it using initial:
mini_form = MinitaskForm(
minitask=minitask,
initial={
"reason": minitask.reason,
"selected_choice": minitask.selected_choice,
},
)

Update the Queryset of a Django-Select2 AutoModelSelect2Field

I can't figure out how to update the queryset of a AutoModelSelect2Field dynamically. I'm getting really strange results. For example, sometimes the select2 box will return the correct, filtered results, and sometimes it will return NO results when I enter the same characters.
my code:
#views.py
form = MyForm()
#forms.py
class MyField(AutoModelSelect2Field):
search_fields = ['name__icontains']
max_results = 10
class MyForm(forms.Form):
my_field = MyField(
queryset=project.objects.none(),
required=True,
widget=AutoHeavySelect2Widget(
select2_options={
'width': '100%',
}
)
)
def __init__(self, *args, **kwargs):
qs = kwargs.pop('queryset')
self.base_fields['my_field'].queryset = qs
super(MyForm, self).__init__(*args, **kwargs)
#self.fields['my_field'].queryset = qs
#self.base_fields['my_field'].queryset = qs
A few of the things I've tried -
update from the view:
#views.py
form = MyForm()
form.base_fields['my_field'].queryset = new_qs
and:
form = MyForm()
form.fields['my_field'].queryset = new_qs
pass the qs to the form:
#views.py
form = MyForm(queryset=Project.objects.filter(project_type=pt))
# see above code for forms.py
I've also tried setting the initial qs to all objects:
class MyForm(forms.Form):
my_field = MyField(
queryset=project.objects,
...
But I get the same problem, 90% of the time I get the results of the initial queryset, rather than the filtered objects based on the new qs.
We were able to find a pretty straightforward way to get the dropdown options to filter by additional fields (ie. first select country and then have the state dropdown only showing states from the selected country)
It was inspired by a suggestion from here (where we also posted this solution):
https://github.com/applegrew/django-select2/issues/22
in forms.py:
class StateChoices(AutoModelSelect2Field):
queryset = State.objects
def get_results(self, request, term, page, context):
country = request.GET.get('country', '')
states = State.objects.filter(country=country, name__istartswith=term)
s2_results = [(s.id, s.name, {}) for s in states]
return ('nil', False, s2_results)
the form field:
# only include this when you would have a screen where the country
# is preset and would not change, but you want to use it as a search context
country = forms.ModelChoiceField(queryset=Country.objects.all(),
widget=forms.HiddenInput())
state = StateChoices(widget = AutoHeavySelect2Widget(
select2_options = {
'minimumInputLength': 1,
'ajax': {
'dataType': 'json',
'quietMillis': 100,
'data': JSFunctionInContext('s2_state_param_gen'),
'results': JSFunctionInContext('django_select2.process_results'),
}
}))
in our javascript:
function s2_state_param_gen(term, page) {
var proxFunc = $.proxy(django_select2.get_url_params, this);
results = proxFunc(term, page, 's2_condition_param_gen');
results.country = $('#id_country').val();
return results;
}

django formset update existing

I want to create something similar as django admin changelist view with list_editable items...
I succeeded in creating the view. But when I post it dies on validation errors.
if request.POST:
formset_class = modelformset_factory(Job)
formset =formset_class(request.POST, request.FILES)
if formset.is_valid():
formset.save()
The problem is that I have only a couple of attributes editable. Therefore some of them are not part of POST and model complains about them being mandatory.
But I want to UPDATE objects not create them. Basically I really want the same thing admin does when having set list_editable but in my own view
I wanted a functionality just like admin using list_editable, so I went for it and pretty much copied the code from options.py of django source. I retrieved admin for my object and then saved original values (function fix_old_job_admin sets them back)
This code solved my problem
job_admin = admin.site._registry[Job]
# save old values so that you can go back to them later
old_list_display = job_admin.list_display
old_list_filter = job_admin.list_filter
old_ordering = job_admin.model._meta.ordering
job_admin.list_editable = ("time", "what", "approved")
cl = ChangeList(request, job_admin.model, job_admin.list_display, job_admin.list_display_links, job_admin.list_filter, job_admin.date_hierarchy, job_admin.search_fields, job_admin.list_select_related, job_admin.list_per_page, job_admin.list_editable,job_admin.admin_site, job_admin)
# options.py from django framework lines 1181-1208 (v. 1.4)
if request.POST:
FormSet = job_admin.get_changelist_formset(request)
formset =FormSet(request.POST, request.FILES, queryset=cl.result_list)
if formset.is_valid():
changecount = 0
for form in formset.forms:
if form.has_changed():
obj = job_admin.save_form(request, form, change=True)
job_admin.save_model(request, obj, form, change=True)
job_admin.save_related(request, form, formsets=[], change=True)
change_msg = job_admin.construct_change_message(request, form, None)
job_admin.log_change(request, obj, change_msg)
changecount += 1
if changecount:
if changecount == 1:
name = force_unicode(job_admin.model._meta.verbose_name)
else:
name = force_unicode(job_admin.model._meta.verbose_name_plural)
msg = ungettext("%(count)s %(name)s was changed successfully.",
"%(count)s %(name)s were changed successfully.",
changecount) % {'count': changecount,
'name': name,
'obj': force_unicode(obj)}
job_admin.message_user(request, msg)
# call function that sets admin with original values
fix_old_job_admin(job_admin, old_list_display, old_ordering, old_list_filter)
return HttpResponseRedirect(request.get_full_path())
FormSet = job_admin.get_changelist_formset(request)
cl.formset = FormSet(queryset=cl.result_list)
context = Context({
'app_label': ContentType.objects.get_for_model(Lawyer).app_label,
'verbose_name_plural': Job._meta.verbose_name_plural.title(),
"cl": cl,
'request': request,
})
# call function that sets admin with original values
fix_old_job_admin(job_admin, old_list_display, old_ordering, old_list_filter)
return render_to_response('yourtemplate/similar_to_changelist.html', context, RequestContext(request))

Categories

Resources