Form fields are empty when created using instance = instance - django

I have two models prodcut_prices and WrongPrice.
In WrongPrice the user can correct report wrong prices - when the price is reported it should also be updated in product_price.
My problem is, even though I instantiate product_price at the very beginning as instance_productprice, all of its required fields returns the "this field has to be filled out" error.
How come those field are not set when im using the instance instance_productprice = product_prices.objects.filter(id=pk)[0] ? Note, that all fields in product_prices are always non-empty since they are being pulled from the product_price model, which is handled in another view, thus that is not the issue.
def wrong_price(request,pk):
#Get the current price object
instance_productprice = product_prices.objects.filter(id=pk)[0]
#Get different values
wrong_link = instance_productprice.link
img_url = instance_productprice.image_url
wrong_price = instance_productprice.last_price
domain = instance_productprice.domain
# Create instances
instance_wrongprice = WrongPrice(
link=wrong_link,
correct_price=wrong_price,
domain = domain)
if request.method == "POST":
form_wrong_price = wrong_price_form(request.POST,instance=instance_wrongprice)
# Update values in product_prices
form_product_price = product_prices_form(request.POST,instance=instance_productprice)
form_product_price.instance.start_price = form_wrong_price.instance.correct_price
form_product_price.instance.last_price = form_wrong_price.instance.correct_price
if form_wrong_price.is_valid() & form_product_price.is_valid():
form_wrong_price.save()
form_product_price.save()
messages.success(request, "Thanks")
return redirect("my_page")
else:
messages.error(request, form_product_price.errors) # Throws empty-field errors,
messages.error(request, form_wrong_price.errors)
return redirect("wrong-price", pk=pk)
else:
form_wrong_price = wrong_price_form(instance=instance_wrongprice)
return render(request, "my_app/wrong_price.html",context={"form":form_wrong_price,"info":{"image_url":img_url}})

I am bit confused about how you implemented it. You have passed a instance of WrongPrice price through the form, which is unnecessary, you could have used initial:
wrong_values = dict(
link=wrong_link,
correct_price=wrong_price,
domain = domain
)
form_wrong_price = wrong_price_form(initial= wrong_values)
Then you are adding values to product_prices_form from instance of form_wrong_price. I don't see why you need a form again here. You can simple use:
form_wrong_price = wrong_price_form(request.POST, initial= wrong_values)
if form_wrong_price.is_valid():
instance = form_wrong_price.save()
instance_productprice.start_price = instance.correct_price
instance_productprice.last_price = instance.correct_price
instance_productprice.save()
Finally, please use PascalCase when defining class names. And you can get the product prices by product_prices.objects.get(id=pk)(instead of filter()[0]).

Related

How do I tackle this issue in my Guess The Flag Django web app

I'm completely new to Django and I'm trying to build this guess the flag web game with it. In main page when someone presses the 'play' button, he's sent to a page where a list of 4 randomly selected countries from the DB is generated, and only of one these 4 countries is the actual answer.
Here's the code from views.py in my App directory :
context = {}
context['submit'] = None
context['countries_list'] = None
score = []
score.clear()
context['score'] = 0
def play(request):
len_score = len(score)
countries = Country.objects.all()
real_choice = None
if request.POST:
get_guess = request.POST.get('guess')
print(request.POST)
if str(get_guess).casefold() == str(context['submit']).casefold():
score.append(1)
else:
score.clear()
len_score = len(score)
choices = random.sample(tuple(countries),4)
real_choice = random.choice(choices)
context['countries_list'] = choices
context['submit'] = real_choice
context['score'] = len_score
return render (request, 'base/play.html', context)
Everything works as expected when there's only one person playing, or the site is opened in only one tab.
The issue here is one someone else opens the site or it's opened in more than one tab, the score gets reset and a new list of random countries is generated for all users, so your guess will never be right!
How do I go about to solve this? Again, I'm completely new to this so I'm left clueless here.
The best way to store short term, per user information is by session variables. This way you can maintain the user's individual settings. So, something like:
def play(request):
len_score = len(score)
countries = Country.objects.all()
real_choice = None
if request.POST:
get_guess = request.POST.get('guess')
print(request.POST)
#Compare guess to session variable
#(using .get() which returns none rather than error if variable not found)
if str(get_guess).casefold() == str(request.session.get('real_choice').casefold():
score.append(1)
else:
score.clear()
len_score = len(score)
choices = random.sample(tuple(countries),4)
real_choice = random.choice(choices)
#Store answer in session variable
request.session['real_choice'] = real_choice
context['countries_list'] = choices
context['submit'] = real_choice
context['score'] = len_score
return render (request, 'base/play.html', context)

How to use timezones in Django Forms

Timezones in Django...
I am not sure why this is so difficult, but I am stumped.
I have a form that is overwriting the UTC dateTime in the database with the localtime of the user. I can't seem to figure out what is causing this.
my settings.py timezone settings look like:
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'America/Toronto'
USE_I18N = True
USE_L10N = False
USE_TZ = True
I am in Winnipeg, my server is hosted in Toronto. My users can be anywhere.
I have a modelfield for each user that is t_zone = models.CharField(max_length=50, default = "America/Winnipeg",) which users can change themselves.
with respect to this model:
class Build(models.Model):
PSScustomer = models.ForeignKey(Customer, on_delete=models.CASCADE)
buildStart = models.DateTimeField(null=True, blank=True)
...
I create a new entry in the DB using view logic like:
...
now = timezone.now()
newBuild = Build(author=machine,
PSScustomer = userCustomer,
buildStart = now,
status = "building",
addedBy = (request.user.first_name + ' ' +request.user.last_name),
...
)
newBuild.save()
buildStart is saved to the database in UTC, and everything is working as expected. When I change a user's timezone in a view with timezone.activate(pytz.timezone(self.request.user.t_zone)) it will display the UTC time in their respective timezone.
All is good (I think) so far.
Here is where things go sideways:
When I want a user to change buildStart in a form, I can't seem to get the form to save the date to the DB in UTC. It will save to the DB in whatever timezone the user has selected as their own.
Using this form:
class EditBuild_building(forms.ModelForm):
buildStart = forms.DateTimeField(input_formats = ['%Y-%m-%dT%H:%M'],widget = forms.DateTimeInput(attrs={'type': 'datetime-local','class': 'form-control'},format='%Y-%m-%dT%H:%M'), label = "Build Start Time")
def __init__(self, *args, **kwargs):# for ensuring fields are not left empty
super(EditBuild_building, self).__init__(*args, **kwargs)
self.fields['buildDescrip'].required = True
class Meta:
model = Build
fields = ['buildDescrip', 'buildStart','buildLength'...]
labels = {
'buildDescrip': ('Build Description'),
'buildStart': ('Build Start Time'),
...
}
widgets = {'buildDescrip': forms.TextInput(attrs={'class': 'required'}),
and this view:
class BuildUpdateView_Building(LoginRequiredMixin,UpdateView):
model = Build
form_class = EditBuild_building
template_name = 'build_edit_building.html'
login_url = 'login'
def get(self, request, *args, **kwargs):
proceed = True
try:
instance = Build.objects.get(id = (self.kwargs['pk']))
except:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
if instance.buildActive == False:
proceed = False
if instance.deleted == True:
proceed = False
#all appears to be well, process request
if proceed == True:
form = self.form_class(instance=instance)
timezone.activate(pytz.timezone(self.request.user.t_zone))
customer = self.request.user.PSScustomer
choices = [(item.id, (str(item.first_name) + ' ' + str(item.last_name))) for item in CustomUser.objects.filter(isDevice=False, PSScustomer = customer)]
choices.insert(0, ('', 'Unconfirmed'))
form.fields['buildStrategyBy'].choices = choices
form.fields['buildProgrammedBy'].choices = choices
form.fields['operator'].choices = choices
form.fields['powder'].queryset = Powder.objects.filter(PSScustomer = customer)
context = {}
context['buildID'] = self.kwargs['pk']
context['build'] = Build.objects.get(id = (self.kwargs['pk']))
return render(request, self.template_name, {'form': form, 'context': context})
else:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer editable here, or has been deleted, please return to dashboard</h2>")
def form_valid(self, form):
timezone.activate(pytz.timezone(self.request.user.t_zone))
proceed = True
try:
instance = Build.objects.get(id = (self.kwargs['pk']))
except:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
if instance.buildActive == False:
proceed = False
if instance.deleted == True:
proceed = False
#all appears to be well, process request
if proceed == True:
form.instance.editedBy = (self.request.user.first_name)+ " " +(self.request.user.last_name)
form.instance.editedDate = timezone.now()
print('edited date ' + str(form.instance.editedDate))
form.instance.reviewed = True
next = self.request.POST['next'] #grabs prev url from form template
form.save()
build = Build.objects.get(id = self.kwargs['pk'])
if build.buildLength >0:
anticipated_end = build.buildStart + (timedelta(hours = float(build.buildLength)))
print(anticipated_end)
else:
anticipated_end = None
build.anticipatedEnd = anticipated_end
build.save()
build_thres_updater(self.kwargs['pk'])#this is function above, it updates threshold alarm counts on the build
return HttpResponseRedirect(next) #returns to this page after valid form submission
else:
return HttpResponse("<h2 style = 'margin:2em;'>This build is no longer available it has been deleted, please please return to dashboard</h2>")
When I open this form, the date and time of buildStart are displayed in my Winnipeg timezone, so Django converted from UTC to my timezone, perfect, but when I submit this form, the date in the DB has been altered from UTC to Winnipeg Time. Why is this?
I have tried to convert the submitted time to UTC in the form_valid function, but this does not seem like the right approach. What am I missing here?
I simply want to store all times as UTC, but display them in the user's timezone in forms/pages.
EDIT
When I remove timezone.activate(pytz.timezone(self.request.user.t_zone)) from both get and form_valid, UTC is preserved in the DB which is great. But the time displayed on the form is now in the default TIME_ZONE in settings.py. I just need this to be in the user's timezone....
EDIT 2
I also tried to add:
{% load tz %}
{% timezone "America/Winnipeg" %}
{{form}}
{% endtimezone %}
Which displayed the time on the form correctly, but then when the form submits, it will again remove 1 hour from the UTC time in the DB.
If I change template to:
{% load tz %}
{% timezone "Europe/Paris" %}
{{form}}
{% endtimezone %}
The time will be displayed in local Paris time. When I submit the form, it will write this Paris time to the DB in UTC+2. So, in summary:
Time record was created was 11:40 Winnipeg time, which writes
16:40 UTC to database, perfect
I access the form template, and time is displayed as local Paris time, 6:40pm, which is also what I would expect.
I submit form without changing any fields.
Record has been updated with the time as 22:40, which is UTC + 6 hours.
What is happening here!?
Put simply: your activate() call in form_valid() comes too late to affect the form field, so the incoming datetime gets interpreted in the default timezone—which in your case is America/Toronto—before being converted to UTC and saved to the database. Hence the apparent time shift.
The documentation doesn't really specify when you need to call activate(). Presumably, though, it has to come before Django converts the string value in the request to the aware Python datetime in the form dictionary (or vice versa when sending a datetime). By the time form_valid() is called, the dictionary of field values is already populated with the Python datetime object.
The most common place to put activate() is in middleware (as in this example from the documentation), since that ensures that it comes before any view processing. Alternatively, if using generic class-based views like you are, you could put it in dispatch().

Why does the render_template keep on showing the old value of the flask form?

I've been searching for an answer for hours. I apologise if I missed something.
I'm using the same form multiple times in order to add rows to my database.
Every time I check an excel file to pre-fill some of the wtforms StringFields with known information that the user may want to change.
The thing is: I change the form.whatever.data and when printing it, it shows the new value. But when I render the template it keeps showing the old value.
I tried to do form.hours_estimate.data = "" before assigning it a new value just in case but it didn't work.
I will attach here the route I'm talking about. The important bit is after # Get form ready for next service. If there's more info needed please let me know.
Thank you very much.
#coordinator_bp.route("/coordinator/generate-order/<string:pev>", methods=['GET', 'POST'])
#login_required
def generate_order_services(pev):
if not (current_user.is_coordinator or current_user.is_manager):
return redirect(url_for('public.home'))
# Get the excel URL
f = open("./app/database/datafile", 'r')
filepath = f.read()
f.close()
error = None
if GenerateServicesForm().submit1.data and GenerateServicesForm().validate():
# First screen submit (validate the data -> first Service introduction)
form = FillServiceForm()
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. Service code not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
start_date = service_info[1]
time_estimate = service_info[2]
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
form.service_code.data = service_code
form.start_date.data = start_date
form.hours_estimate.data = time_estimate
return render_template('coordinator/fill_service_form.html', form=form, error=error, assembly_types=assembly_types)
if FillServiceForm().submit2.data:
if not FillServiceForm().validate():
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
return render_template('coordinator/fill_service_form.html', form=FillServiceForm(), error=error,
assembly_types=assembly_types)
# Service screen submits
# Here we save the data of the last submit and ready the next one or end the generation process
# Ready the form
form = FillServiceForm()
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. Service code not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
form.service_code.data = service_code
# create the service (this deletes the service code from the excel)
service = create_service(form, filepath)
if isinstance(service,str):
return render_template('coordinator/fill_service_form.html', form=form, error=service)
# Get next service
next_service_row = get_next_service_row(filepath)
if next_service_row is None:
# This means there is no more services pending
return "ALL DONE"
else:
# Get form ready for next service
service_info = get_service_info(filepath, next_service_row)
service_code = service_info[0]
start_date = service_info[1]
time_estimate = service_info[2]
print("time_estimate")
print(time_estimate) # I get the new value.
objects = AssemblyType.get_all()
assembly_types = []
for assembly_type in objects:
assembly_types.append(assembly_type.type)
form.service_code.data = service_code
form.start_date.data = start_date
form.hours_estimate.data = time_estimate
print(form.hours_estimate.data) # Here I get the new value. Everything should be fine.
# In the html, the old value keeps on popping.
return render_template('coordinator/fill_service_form.html', form=form, error=error,
assembly_types=assembly_types)
number_of_services = excel_get_services(filepath=filepath, selected_pev=pev)
# Get the number of the first excel row of the selected pev
first_row = excel_get_row(filepath, pev)
if first_row is None:
excel_info = excel_get_pev(filepath)
error = "Excel error. PEV not found. If you get this error please report the exact way you did it."
return render_template('coordinator/get_pev_form.html', form=GetPevForm(), error=error, info=excel_info)
service_code = []
start_date = []
time_estimate_code = []
quantity = []
# Open the excel
wb = load_workbook(filepath)
# grab the active worksheet
ws = wb.active
for idx in range(number_of_services):
# Append the data to the lists
service_code.append(ws.cell(row=first_row+idx, column=12).value)
start_date.append(str(ws.cell(row=first_row + idx, column=5).value)[:10])
time_estimate_code.append(ws.cell(row=first_row+idx, column=7).value)
quantity.append(ws.cell(row=first_row + idx, column=9).value)
wb.close()
return render_template('coordinator/generate_services_form.html',
form=GenerateServicesForm(),
pev=pev,
service_code=service_code,
start_date=start_date,
time_estimate_code=time_estimate_code,
quantity=quantity)
Well I found a workarround: I send the data outside the form like this:
return render_template('coordinator/fill_service_form.html', form=form, error=error,
assembly_types=assembly_types,
service_code=service_code,
start_date=start_date,
time_estimate=time_estimate)
And replace the jinja form for this:
<input class="form-control" placeholder="2021-04-23" name="start_date" type="text" value="{{start_date}}">
I'm still using the form (name= the form field name) and at the same time I input the value externally.
I hope this helps somebody.

Set default value for dynamic choice field

I have a form that asks the user to enter in their zip code. Once they do it sends them to another form where there is a field called 'pickup_date'. This gets the value of the zip from the previous field and gets all of the available pickup_dates that match that zip code into a ChoiceField. I set all of this within the init of the model form.
def __init__(self,*args,**kwargs):
super(ExternalDonateForm,self).__init__(*args,**kwargs)
if kwargs:
zip = kwargs['initial']['zip']
self.fields['pickup_date'] = forms.ChoiceField(choices = self.get_dates(zip))
elif self.errors:
zip = self.data['zip']
self.fields['pickup_date'] = forms.ChoiceField(choices = self.get_dates(zip))
The problem I have is when there are other errors on the form. I use the elif self.errors to regenerate the possible choices but it doesn't default to the original selected option. It goes back and defaults to the first choice. How can I make it so it's default option on form errors is what was originally posted?
Change self.fields['pickup_date'] to self.fields['pickup_date'].initial and see if that helps.
I got it to work after playing around for a while. Above, I was setting all the dynamic choices with a get_dates() function that returned a tuple. Instead of doing that I returned a field object like this using a customized ModelChoiceField instead of a regular ChoiceField....
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.date.strftime('%a %b %d, %Y')
Dates function
def get_dates(self,zip):
routes = Route.objects.filter(zip=zip).values_list('route',flat=True)
pickups = self.MyModelChoiceField(queryset = PickupSchedule.objects.filter(
current_count__lt=F('specials'),
route__in=routes,
).order_by('date')
)
if not pickups:
pickups = (('----','No Pickups Available At This Time'),)
return pickups
in the init i set the value for self.fields['pickup_date'] like so..
self.fields['pickup_date'] = self.get_dates(zip)

Django custom form validation best practices?

I have a form that contains 5 pairs of locations and descriptions. I have three sets of validations that need to be done
you need to enter at least one location
for the first location, you must have a description
for each remaining pair of locations and description
After reading the Django documentation, I came up with the following code to do these custom validations
def clean(self):
cleaned_data = self.cleaned_data
location1 = cleaned_data.get('location1')
location2 = cleaned_data.get('location2')
location3 = cleaned_data.get('location3')
location4 = cleaned_data.get('location4')
location5 = cleaned_data.get('location5')
description1 = cleaned_data.get('description1')
description2 = cleaned_data.get('description2')
description3 = cleaned_data.get('description3')
description4 = cleaned_data.get('description4')
description5 = cleaned_data.get('description5')
invalid_pairs_msg = u"You must specify a location and description"
# We need to make sure that we have pairs of locations and descriptions
if not location1:
self._errors['location1'] = ErrorList([u"At least one location is required"])
if location1 and not description1:
self._errors['description1'] = ErrorList([u"Description for this location required"])
if (description2 and not location2) or (location2 and not description2):
self._errors['description2'] = ErrorList([invalid_pairs_msg])
if (description3 and not location3) or (location3 and not description3):
self._errors['description3'] = ErrorList([invalid_pairs_msg])
if (description4 and not location4) or (location4 and not description4):
self._errors['description4'] = ErrorList([invalid_pairs_msg])
if (description5 and not location5) or (location5 and not description5):
self._errors['description5'] = ErrorList([invalid_pairs_msg])
return cleaned_data
Now, it works but it looks really ugly. I'm looking for a more "Pythonic" and "Djangoist"(?) way to do this. Thanks in advance.
First thing you can do is simplify your testing for those cases where you want to see if only one of the two fields is populated. You can implement logical xor this way:
if bool(description2) != bool(location2):
or this way:
if bool(description2) ^ bool(location2):
I also think this would be more clear if you implemented a clean method for each field separately, as explained in the docs. This makes sure the error will show up on the right field and lets you just raise a forms.ValidationError rather than accessing the _errors object directly.
For example:
def _require_together(self, field1, field2):
a = self.cleaned_data.get(field1)
b = self.cleaned_data.get(field2)
if bool(a) ^ bool(b):
raise forms.ValidationError(u'You must specify a location and description')
return a
# use clean_description1 rather than clean_location1 since
# we want the error to be on description1
def clean_description1(self):
return _require_together('description1', 'location1')
def clean_description2(self):
return _require_together('description2', 'location2')
def clean_description3(self):
return _require_together('description3', 'location3')
def clean_description4(self):
return _require_together('description4', 'location4')
def clean_description5(self):
return _require_together('description5', 'location5')
In order to get the behavior where location1 is required, just use required=True for that field and it'll be handled automatically.
At least you can reduce some code. Have 'location1' and 'description1' Required=True (as TM and stefanw pointed out). Then,
def clean(self):
n=5
data = self.cleaned_data
l_d = [(data.get('location'+i),data.get('description'+i)) for i in xrange(1,n+1)]
invalid_pairs_msg = u"You must specify a location and description"
for i in xrange(1,n+1):
if (l_d[i][1] and not l_d[i][0]) or (l_d[i][0] and not l_d[i][1]):
self._errors['description'+i] = ErrorList([invalid_pairs_msg])
return data
still ugly though...