So i worte a function in Web2py to create a record into a table in the Database under a Condition, but Web2py create the record allthough that condition is not filled,
here is the function
def buy_product():
price = price_set(db,auth,'product')
balance = limit(db,auth,'settings','account_balance')
if balance !=None:
if balance < price:
form=redirect(URL('order'))
else:
form=crud.create(db.letter)
if form.accepts(request.vars, session):
tax = float(postage(form.vars.tax_type).replace("-","."))
##########################
# I'm talking about this #
##########################
if balance < (price + tax):
response.flash='You don\'t have enough balance to buy this product'
redirect(URL('not_processed'))
else:
function_1(....)
...
...
update_field(db,auth,'settings','account_balance',-price)
response.flash='Done'
redirect(URL('products'))
pass
elif form.errors:
response.flash='Error 01'
else:
pass
###############################
else:
form=redirect(URL('settings'))
return dict(form=form)
it's sepposed that when the Balance < price + tax the user should be redirected to not_processed without creating new record in the database.
but web2py redirect the user to not_processed and create the record without executing this part with the entered information from the user. so the user see that he bought something, when it has not processed (see below)
function_1(....)
...
...
update_field(db,auth,'settings','account_balance',-price)
response.flash='Done'
redirect(URL('products'))
pass
any idea ??
thank you
Crud manages inserts/updates internally and it does not use the form.accepts.
You have two options:
1 - Use SQLFORM
form=SQLFORM(db.letter)
if form.accepts(request.vars, session):
tax = float(postage(form.vars.tax_type).replace("-","."))
##########################
# I'm talking about this #
##########################
2 - Use crud events
def myfunction(form):
# do your stuff here
# it will be called only when form is accepted
def myotherfunction(form):
if form.errors:
#do something here in case of errors
#it will be called during the form validation
crud.settings.create_onvalidation = myotherfunction
crud.settings.create_onaccept = myfunction
#the above can be:
#crud.create_onaccept = lambda form: myfunction(form)
# define the above events before the creation of the form
form=crud.create(db.letter)
Note that SQLFORM is a bit different of crud, SQLFORM expects you to validate the form in form.accepts method, while crud does it internally and uses events as onvalidation, onaccept to custom validations.
Related
Okay so I have this fixture:
#pytest.fixture
def dummy_name():
def func(name="Dummy Name"):
num = 2
while True:
yield name
if num > 2:
tags = name.rsplit("_", 1)
name = f"{tags[0]}_{num}"
else:
name += f"_{num}"
num += 1
return func
#pytest.fixture
def dummy_admin_name(dummy_name):
return dummy_name(name="Dummy Admin")
I can use it like this:
def some_function(dummy_admin_name):
print(next(dummy_admin_name))
print(next(dummy_admin_name))
print(next(dummy_admin_name))
Which returns:
Dummy Admin
Dummy Admin_2
Dummy Admin_3
But the problem is I have to create that secondary fixture all the time for different strings, because calling the the base dummy_name fixture doesn't work:
def some_function(dummy_name):
print(next(dummy_name(name="Dummy Admin")))
print(next(dummy_name(name="Dummy Admin")))
print(next(dummy_name(name="Dummy Admin")))
Which returns:
Dummy Admin
Dummy Admin
Dummy Admin
Clearly because adding the argument three times resets the name value every time.
How could I make that input persistent for future calls without a new fixture?
As you wrote, you cannot call the fixture multiple times, as it will re-create
the function each time. However, you could just create the callable before using it:
def test_something(dummy_name):
dummy_admin = dummy_name(name="Dummy Admin")
print(next(dummy_admin))
print(next(dummy_admin))
print(next(dummy_admin))
dummy_user = dummy_name(name="Dummy User")
print(next(dummy_user))
print(next(dummy_user))
which prints:
Dummy Admin
Dummy Admin_2
Dummy Admin_3
Dummy User
Dummy User_2
i'm trying to do a method with a button the add the total value of a Bill to the value of another object it working well but every time I press the button it adds the total value of the bill to the other object value, I need anything make scenes to prevent adding the bill value multiple times , here is the code
class acc_inv_prop(models.Model):
_inherit = 'account.invoice'
property_id = fields.Many2one(comodel_name="account.asset.asset", string="Property", required=False, )
def prop_up_value(self):
self.property_id.value += self.amount_total
pass
what I can do on the object account.asset.asset to check the current bill was added before and raise error and if the bill is a new one add it's value normally but also for one time only
any help will be appreciated
Besides adding something to the value of an account.asset.asset making me uncomfortable (Shouldn't you make a logic that determines the full value of the asset from the invoice?)
You would need an additional field to remember that you added the amount already:
class acc_inv_prop(models.Model):
_inherit = 'account.invoice'
property_id = fields.Many2one(comodel_name="account.asset.asset", string="Property", required=False, )
property_last_added_value = fields.Float(string='Last Value Added to Property', readonly=True, digits=0)
def prop_up_value(self):
if self.property_last_added_value != self.amount_total:
self.property_id.value -= self.property_last_added_value
self.property_id.value += self.amount_total
self.property_last_added_value = self.amount_total
I am using a wagtail_hook to update a Page object and am running into trouble. More specifically, when an end-user hits the "Save draft" button from the browser I want the following code to fire. The purpose of this code is to change the knowledge_base_id dynamically, based on the results of the conditional statements listed below.
def sync_knowledge_base_page_with_zendesk2(request, page):
if isinstance(page, ContentPage):
page_instance = ContentPage.objects.get(pk=page.pk)
pageJsonHolder = page_instance.get_latest_revision().content_json
content = json.loads(pageJsonHolder)
print("content at the start = ")
print(content['knowledge_base_id'])
kb_id_revision = content['knowledge_base_id']
kb_active_revision = content['kb_active']
if kb_active_revision == "T":
if kb_id_revision == 0:
print("create the article")
content['knowledge_base_id'] = 456
#page_instance.knowledge_base_id = 456 # change this API call
else:
print("update the article")
elif kb_id_revision != 0:
print("delete the article")
content['knowledge_base_id'] = 0
#page_instance.knowledge_base_id = 0
print("content at the end = ")
print(content['knowledge_base_id'])
#page_instance.save_revision().publish
So when the hook code fires, it updates the draft with all the info EXCEPT the knowledge_base_id.
However when I change the knowledge_base_id like this (seen commented out above)
page_instance.knowledge_base_id = 0
And save it like this (also seen commented out above)
page_instance.save_revision().publish()
It saves the updated knowledge_base_id BUT skips over the other revisions. In short, what the heck am I doing wrong. Thanks in advance for the assist. Take care and have a good day.
So I figured out the problem. Instead of trying to use the Page method save_revisions(), I opted to use revisions.create(). Inside revisions.create(), you pass it a JSON object with your updated values. In addition to that you pass an instance of the user, and values for submitted_for_moderation and approved_go_live_at. Listed below is my updated code sample, with comments. Please let me know if you have any questions for me. I hope this post helps others avoid frustrations with updating revisions. Thanks for reading. Take care and have a good day.
from wagtail.wagtailcore import hooks
from .models import ContentPage
import json
# Allows the latest page revision JSON to be updated based on conditionals
def sync_kb_page_with_zendesk(request, page):
# Sanity check to ensure page is an instance of ContentPage
if isinstance(page, ContentPage):
# this sets the user variable
user_var = request.user
# sets the Content Page
page_instance = ContentPage.objects.get(pk=page.pk)
# this retrieves JSON str w/ latest revisions
pageJsonHolder = page_instance.get_latest_revision().content_json
# this takes the json string and converts it into a json object
content = json.loads(pageJsonHolder)
# this sets the kb id variable for use in the code
kb_id_revision = content['knowledge_base_id']
# this sets the kb active variable for use in the code
kb_active_revision = content['kb_active']
# this is the conditional logic
if kb_active_revision == "T":
if kb_id_revision == 0:
print("create the article")
# updates the kb id value in the JSON object
content['knowledge_base_id'] = 456
else:
print("update the article")
elif kb_id_revision != 0:
print("delete the article")
# updates the kb id value in the JSON object
content['knowledge_base_id'] = 0
# this takes the JSON object and coverts it back to a JSON str
revisionPageJsonHolder = json.dumps(content)
# this takes your JSON str and creates the latest revision of Page
revision = page_instance.revisions.create(
content_json=revisionPageJsonHolder,
user=user_var,
submitted_for_moderation=False,
approved_go_live_at=None,
)
# registers the function to fire after page edit
hooks.register('after_edit_page', sync_kb_page_with_zendesk)
# registers the function to fire after page creation
hooks.register('after_create_page', sync_kb_page_with_zendesk)
I'm getting a strange error in my Django testing code.
Full code:
from .models import MIN_BIRTH_YEAR
from .models import UserProfile
from django.contrib.auth.models import User
from django.test import TestCase
import factory
TEST_USERS = []
TEST_PASSWORD = 'password123abc'
class UserProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = UserProfile
birth_year = factory.Sequence(lambda n: n + MIN_BIRTH_YEAR - 1)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
profile = factory.RelatedFactory(UserProfileFactory, 'user')
username = factory.Sequence(lambda n: 'test_username{}'.format(n))
first_name = factory.Sequence(lambda n: 'test_first_name{}'.format(n))
last_name = factory.Sequence(lambda n: 'test_last_name{}'.format(n))
email = factory.Sequence(lambda n: 'test_email{}#example.com'.format(n))
password = factory.PostGenerationMethodCall('set_password', TEST_PASSWORD)
def create_insert_test_users():
for i in range(5):
TEST_USERS.append(UserFactory.create())
def _test_one_logged_in_user(test_instance, test_user_index):
"""
In addition to public information, private content for a single
logged-in user should be somewhere on the page.
"""
test_instance.client.logout()
test_user = TEST_USERS[test_user_index]
print('Attempting to login:')
profile = test_user.profile
print('test_user.id=' + str(test_user.id))
print(' username=' + test_user.username + ', password=' + TEST_PASSWORD)
print(' first_name=' + test_user.first_name + ', last_name=' + test_user.last_name)
print(' email=' + test_user.email)
print(' profile=' + str(profile))
print(' profile.birth_year=' + str(profile.birth_year))
Continued. This is the login line I'm talking about. This _test_one_logged_in_user function is called by the second to last line (_test_one_logged_in_user(self, 0)) below:
did_login_succeed = test_instance.client.login(
username=test_user.username,
password=TEST_PASSWORD)
test_instance.assertTrue(did_login_succeed)
##########################################
# GET PAGE AND TEST ITS CONTENTS HERE...
##########################################
class MainPageTestCase(TestCase):
"""Tests for the main page."""
def setUp(self_ignored):
"""Insert test users."""
create_insert_test_users()
def test_true_is_true(self):
"""Public information should be somewhere on the page."""
self.assertTrue(True)
def test_logged_in_users(self):
"""
In addition to public information, private content for logged in
users should also be somewhere on the page.
"""
_test_one_logged_in_user(self, 0)
_test_one_logged_in_user(self, 1)
This works fine. Everything passes. But change the name of test_true_is_true to test_content_not_logged_in
def test_content_not_logged_in(self):
"""Public information should be somewhere on the page."""
self.assertTrue(True)
and test_instance.client.login now returns False...which results in the assertion below it
test_instance.assertTrue(did_login_succeed)
to fail: AssertionError: False is not true. If you comment out the entire function, though, it succeeds (login returns True).
# def test_content_not_logged_in(self):
# """Public information should be somewhere on the page."""
# self.assertTrue(True)
If you uncomment it and rename it to any of the following, it works:
test_xcontent_not_logged_in
test__content_not_logged_in
test_not_logged_in
Any of these, and it fails:
test_ctrue_is_true
test_cxontent_not_logged_in
test_contentnot_logged_in
test_contennot_logged_in
test_contenot_logged_in
test_contnot_logged_in
test_connot_logged_in
test_cnot_logged_in
test_c
(I've searched for test_c and found something but nothing that indicates anything particularly special.)
This seems to imply that the setUp function runs once for test_content_not_logged_in (the trivial function), and then again for test_logged_in_users. And this running twice is causing problems. So I changed it so the users are only created if the TEST_USER array is empty:
def create_insert_test_users():
if len(TEST_USERS) == 0:
for i in range(5):
TEST_USERS.append(UserFactory.create())
But it's still failing, and I can confirm it's failing with the user having an id of 1:
$ python -Wall manage.py test auth_lifecycle.test__view_main2
/home/jeffy/django_files/django_auth_lifecycle_venv/lib/python3.4/site.py:165: DeprecationWarning: 'U' mode is deprecated
f = open(fullname, "rU")
/home/jeffy/django_files/django_auth_lifecycle_venv/lib/python3.4/imp.py:32: PendingDeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
PendingDeprecationWarning)
Creating test database for alias 'default'...
.Attempting to login:
test_user.id=1
username=test_username1, password=password123abc
first_name=test_first_name1, last_name=test_last_name1
email=test_email1#example.com
profile=test_username1
profile.birth_year=1887
F
======================================================================
FAIL: test_logged_in_users (auth_lifecycle.test__view_main2.MainPageTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/jeffy/django_files/django_auth_lifecycle/auth_lifecycle/test__view_main2.py", line 74, in test_logged_in_users
_test_one_logged_in_user(self, 0)
File "/home/jeffy/django_files/django_auth_lifecycle/auth_lifecycle/test__view_main2.py", line 53, in _test_one_logged_in_user
test_instance.assertTrue(did_login_succeed)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 2 tests in 0.385s
FAILED (failures=1)
Destroying test database for alias 'default'...
models.py:
"""Defines a single extra user-profile field for the user-authentication
lifecycle demo project:
- Birth year, which must be between <link to MIN_BIRTH_YEAR> and
<link to MAX_BIRTH_YEAR>, inclusive.
"""
from datetime import datetime
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models
OLDEST_EVER_AGE = 127 #:Equal to `127`
YOUNGEST_ALLOWED_IN_SYSTEM_AGE = 13 #:Equal to `13`
MAX_BIRTH_YEAR = datetime.now().year - YOUNGEST_ALLOWED_IN_SYSTEM_AGE
"""Most recent allowed birth year for (youngest) users."""
MIN_BIRTH_YEAR = datetime.now().year - OLDEST_EVER_AGE
"""Most distant allowed birth year for (oldest) users."""
def _validate_birth_year(birth_year_str):
"""Validator for <link to UserProfile.birth_year>, ensuring the
selected year is between <link to OLDEST_EVER_AGE> and
<link to MAX_BIRTH_YEAR>, inclusive.
Raises:
ValidationError: When the selected year is invalid.
https://docs.djangoproject.com/en/1.7/ref/validators/
I am a recovered Hungarian Notation junkie (I come from Java). I
stopped using it long before I started with Python. In this
particular function, however, because of the necessary cast, it's
appropriate.
"""
birth_year_int = -1
try:
birth_year_int = int(str(birth_year_str).strip())
except TypeError:
raise ValidationError(u'"{0}" is not an integer'.format(birth_year_str))
if not (MIN_BIRTH_YEAR <= birth_year_int <= MAX_BIRTH_YEAR):
message = (u'{0} is an invalid birth year.'
u'Must be between {1} and {2}, inclusive')
raise ValidationError(message.format(
birth_year_str, MIN_BIRTH_YEAR, MAX_BIRTH_YEAR))
#It's all good.
class UserProfile(models.Model):
"""Extra information about a user: Birth year.
---NOTES---
Useful related SQL:
- `select id from auth_user where username <> 'admin';`
- `select * from auth_lifecycle_userprofile where user_id=(x,x,...);`
"""
# This line is required. Links UserProfile to a User model instance.
user = models.OneToOneField(User, related_name="profile")
# The additional attributes we wish to include.
birth_year = models.IntegerField(
blank=True,
verbose_name="Year you were born",
validators=[_validate_birth_year])
# Override the __str__() method to return out something meaningful
def __str__(self):
return self.user.username
When you change the name of the test, you change the order in which the tests run. The test_logged_in_users method runs BEFORE test_true_is_true but runs AFTER test_c_whatever (presumably because it's running them in alpha or some sort of order). This is why you're seeing the weirdness with the name changes.
As you figured out, your setUp method runs for each test case. When your setUp runs the first time, Users are created and saved to both the DB and TEST_USERS. When your second test runs, your DB is refreshed, and all your users are deleted. The users represented by TEST_USERS (which are still in your list, because your globals persist across test cases) no longer exist in the DB.
You can make your test pass in your original code by resetting TEST_USERS, like this:
def create_insert_test_users():
# global tells python to use the TEST_USERS above, not create a new one
global TEST_USERS
TEST_USERS = []
# Your code here...
Now, TEST_USERS represents new, real users that match users in the DB. Generally speaking, though, globals are a bad idea (for several reasons, the confusion you're experiencing being among them). Creating them on-the-fly (as you're working toward in your latest update) is a much better solution.
TestCase will recognize all the tests by looking for methods that start with test
From documentation:
The individual tests are defined with methods whose names start
with the letters test. This naming convention informs the test runner
about which methods represent tests.
So when you rename a_trivial_function it changes whether it is considered a test or not.
The original code stores TEST_USERS locally, and it seems that statically-held object was causing problems when shared among the tests. I naively thought it was important to store the objects locally, in order to compare the database values against it. That implies I don't trust Django or Factory Boy to correctly insert them into the database, and they can handle that just fine.
Here's the updated code, which only stores the objects in the database. I also moved the contents sub-function containing login directly into the bottom function.
from .models import MIN_BIRTH_YEAR
from .models import UserProfile
from django.contrib.auth.models import User
from django.test import TestCase
import factory
TEST_PASSWORD = 'password123abc'
TEST_USER_COUNT = 5
class UserProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = UserProfile
birth_year = factory.Sequence(lambda n: n + MIN_BIRTH_YEAR - 1)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
profile = factory.RelatedFactory(UserProfileFactory, 'user')
username = factory.Sequence(lambda n: 'test_username{}'.format(n))
#print('username=' + username)
first_name = factory.Sequence(lambda n: 'test_first_name{}'.format(n))
last_name = factory.Sequence(lambda n: 'test_last_name{}'.format(n))
email = factory.Sequence(lambda n: 'test_email{}#example.com'.format(n))
password = factory.PostGenerationMethodCall('set_password', TEST_PASSWORD)
class MainPageTestCase(TestCase):
"""Tests for the main page."""
def setUp(self_ignored):
"""Insert test users."""
#print('a User.objects.count()=' + str(User.objects.count()))
#http://factoryboy.readthedocs.org/en/latest/reference.html?highlight=create#factory.create_batch
UserFactory.create_batch(TEST_USER_COUNT)
#print('b User.objects.count()=' + str(User.objects.count()))
Continued:
def test_ctrue_is_true(self):
"""Public information should be somewhere on the page."""
self.assertTrue(True)
def test_some_logged_in_users(self):
"""
In addition to public information, private content for logged in
users should also be somewhere on the page.
"""
for n in range(2):
self.client.logout()
test_user = UserFactory()
print('Attempting to login:')
profile = test_user.profile
print('test_user.id=' + str(test_user.id))
print(' username=' + test_user.username + ', password=' + TEST_PASSWORD)
print(' first_name=' + test_user.first_name + ', last_name=' + test_user.last_name)
print(' email=' + test_user.email)
print(' profile=' + str(profile))
print(' profile.birth_year=' + str(profile.birth_year))
did_login_succeed = self.client.login(
username=test_user.username,
password=TEST_PASSWORD)
self.assertTrue(did_login_succeed)
##########################################
# GET PAGE AND TEST ITS CONTENTS HERE...
##########################################
I have a form with checkboxes, the form functioning well, in my view i can use request.POST.getlist('list') to retrieve the list of values.
At the moment am trying to do some form validation inside the clean method and when i try to use self.cleaned_data['list'] I get the last value. I cannot retrieve the list of items.
Any idea how i could do that?
forms.py
class SelectList_Form(forms.Form):
list = forms.CharField(required=False)
def clean(self):
super(SelectList_Form, self).clean()
cleaned_data = self.cleaned_data
try:
# TODO: list validation
if cleaned_data['list'].__len__() is 0:
raise forms.ValidationError(_('Must select at least one of the lists below'),)
if cleaned_data['list'].__len__() > 1:
try:
# In here when i print list it only shows me the last value. It doesn't show me the list of values when the box is checked
print cleaned_data['list']
except Main.DoesNotExist:
raise Http404
except forms.ValidationError:
raise
class Posting_Wizard(FormWizard):
def render_template(self, request, form, previous_fields, step, context=None):
if step == 0:
obj = MainI18n.objects.filter(main__is_active=True, language=request.LANGUAGE_CODE).\
exclude(main__parent=None).order_by('main__parent').select_related(depth=1)
category_choices=dict(['%s,%s' % (i.main.slug, i.main.parent.slug), '%s - %s' % (i.main.parent,i.label)] for i in obj)
form.fields['categories'] = forms.CharField(widget=forms.RadioSelect(choices=category_choices.items()))
if step == 1:
category = request.POST.get('0-categories')
pobj = Main.objects.filter(slug=category.split(',')[1], parent=None).get()
cobj = Main.objects.filter(slug=category.split(',')[0], parent=pobj.id).get()
lobj = ListI18n.objects.filter(list__is_active=True, language=request.LANGUAGE_CODE, list__main__slug=category.split(',')[0], list__main__parent=pobj.id).select_related()
list_choices = dict([i.id, i.title] for i in lobj)
if cobj.mainproperties.relation == 'M':
# Here i generate the checkboxes
form.fields['list']=forms.CharField(widget=forms.CheckboxSelectMultiple(choices=list_choices.items()),label="Pick the list",)
else:
form.fields['list']=forms.CharField(widget=forms.RadioSelect(choices=list_choices.items()),label="Pick the list",)
return super(Posting_Wizard, self).render_template(request, form, previous_fields, step, context)
def done(self, request, form_list):
return HttpResponseRedirect(reverse('accounts-registration-wizard-done'))
def get_template(self, step):
return 'listing/post/wizard/wizard_%s.html' % step
First, there are a number of basic Python errors here. There is almost never a need to access the double-underscore functions - they are internal implementation details. Always use the normal len() function instead. And, never use is for comparisons: it's for identity, so should only be used with things you know have the same identity, which basically just means None. So your code should read:
if len(cleaned_data['list']) == 0:
etc.
Now, secondly, I don't understand why you think there could ever be more than one 'element' in list. You've defined it as a CharField, which is a single field containing many characters. Your len is testing the number of characters entered into that field, not the number of fields, however you think you've defined them.