I have written code for managing conditional insert/update/delete to
multiple tables from single form in 'web2py'.
I agree, the code is in very raw form & may not be ‘pythonic’.
There are code repeatitions.
But at least I have something to go ahead & build a refined
structure.
MODELS:
db.define_table('mdlmst',
Field('mdlmstid','id'),
Field('mdlmstcd'),
Field('mdlmstnm'),
migrate=False,
format='%(mdlmstnm)s'
)
db.define_table('wrmst',
Field('wrmstid','id'),
Field('wrmstcd'),
Field('wrmstnm'),
migrate=False,
format='%(wrmstnm)s'
)
db.define_table('extwrmst',
Field('extwrmstid','id'),
Field('extwrmstcd'),
Field('extwrmstnm'),
migrate=False,
format='%(extwrmstnm)s'
)
from the FORM, data will be populated in the following two tables
db.define_table('mdlwr',
Field('mdlwrid','id'),
Field('mdlmstid',db.mdlmst),
Field('wrmstid',db.wrmst),
migrate=False
)
db.define_table('mdlextwr',
Field('mdlextwrid','id'),
Field('mdlmstid',db.mdlmst),
Field('extwrmstid',db.extwrmst),
migrate=False
)
CONTROLLERS:
‘modelwar’ controller will render the records from ‘mdlmst’ table
def modelwar():
models = db(db.mdlmst.mdlmstid>0).select(orderby=db.mdlmst.mdlmstnm)
return dict(models=models)
after clicking a particular record, ‘war_edit’ controller will
manage the tables – ‘mdlwr’ & ‘mdlextwr’
def war_edit():
mdl_id = request.args(0)
mdl_id is a variable identifying the ‘mdlmstid’ (which record to be modified)
mdl_nm = request.args(1)
mdl_nm is a variable for getting the ‘mdlmstnm’
warset = db(db.mdlwr.mdlmstid==mdl_id) # fetch a set
extwarset = db(db.mdlextwr.mdlmstid==mdl_id) # fetch a set
warlist = db(db.mdlwr.mdlmstid==mdl_id).select() # get a ROW object
extwarlist = db(db.mdlextwr.mdlmstid==mdl_id).select() # get a ROW object
form_war=FORM(TABLE(TR("Basic Warranty",
SELECT(_type="select",_name="baswar",*[OPTION(x.wrmstnm,_value=x.wrmstid)
for x in db().select(db.wrmst.ALL)]),
TR("Extended Warranty",
SELECT(_type="select",_name="extwar",*[OPTION(x.extwrmstnm,_value=x.extwrmstid)
for x in db().select(db.extwrmst.ALL)]),
TR("", INPUT(_type='submit',_value='Save')), ))))
pre-populate the fields in‘form_war’
if len(warlist)>0:
form_war.vars.baswar = warlist[0].wrmstid
if len(extwarlist)>0:
form_war.vars.extwar = extwarlist[0].extwrmstid
after successful form submission, manage the table 'mdlwr'
if form_war.accepts(request.vars, session):
if there was any record in the list fetched from database & sent to FORM,
if len(warlist)>0:
delete if value returned from FORM field is blank, else update
if form_war.vars.baswar==''
warset.delete()
else:
warset.update(wrmstid=form_war.vars.baswar)
else insert
else:
db.mdlwr.insert(mdlmstid=mdl_id, wrmstid=form_war.vars.baswar)
Similarly, manage the table 'mdlextwr'
if len(extwarlist)>0:
if form_war.vars.extwar=='':
extwarset.delete()
else:
extwarset.update(extwrmstid=form_war.vars.extwar)
else:
db.mdlextwr.insert(mdlmstid=mdl_id, extwrmstid=form_war.vars.extwar)
response.flash = 'Warranty definition saved'
return dict(form_war=form_war,mdlnm=mdl_nm)
VIEW for 'mdlmst' table
{{response.files.append(URL(r=request,c='static',f='jquery.dataTables.min.j
s'))}}
{{response.files.append(URL(r=request,c='static',f='demo_table.css'))}}
{{extend 'layout.html'}}
jQuery(document).ready(function()
{ jQuery('.smarttable').dataTable();});
Modelwise Warranty Master
Model IDModel CodeModel Name
{{for model in models:}}
{{=model.mdlmstid}}
{{=model.mdlmstcd}}
{{=model.mdlmstnm}}
{{=A('edit
warranty',_href=URL('war_edit',args=[model.mdlmstid,model.mdlmstnm]))}}
{{pass}}
Pl. tell me if I have coded anything stupid here.
I would highly welcome any ideas/suggestions for improvements.
Thanks,
Vineet
Your database design looks strange to me.
In each table you have a field of type 'id'. This will replace the id field automatically generated by web2py - a bad idea. From the web2py book: "Do not declare a field called "id", because one is created by web2py anyway. Every table has a field called "id" by default. It is an auto-increment integer field (starting at 1) used for cross-reference and for making every record unique, so "id" is a primary key"
You have created a many to many relationship between table 'mdlmst' and 'wrmst' and another many to many relationship between 'mdlmst' and 'extwrmst'. While this is not necessarily wrong, it strikes me as extremely unlikely this is what you want.
My feeling is that your database design needs work. This should be sorted out before you start designing forms.
Related
I have created a flask application and using mysql as DB backend and this is used by multiple users simultaneously.
The problem I'm having is,In my homepage a select query is performed and data is displayed to the user but same data is showing to all users.it should be unique. I have tried to lock the row by using FOR UPDATE while selecting the row. I know that I'm not updating the row,so the transaction will be closed when the function ends and the row will be released from lock.
How to overcome this problem?
Expected output: Each user should get different data from the table.(Even when they refresh)
#is_logged_in
#app.route('/')
def index():
conn = mysql.connection
cur = conn.cursor()
cur.execute("select mylist ,myurl ,swatch,parent from image_links where status =%s LIMIT 1 FOR UPDATE",("fetched",))
parent = cur.fetchall()
for row in parent:
mylistitems = row[0].split(",")
swatches = row[2].split(",")
myurlsitems = row[1].split(",")
pid = row[3]
if asinlist != ['']:
merged = tuple(zip(mylistitems ,myurlsitems ,swatches))
return render_template('home.html',firstimage= myurlsitems[0],merged=merged)
else:
cur.execute("UPDATE asin_links SET status = %s WHERE pid= %s", ("invalid",pid,))
conn.commit()
return redirect(url_for('index'))
I can't see any "current user" specific parameters used in your sql query or any data filtering decided on some user ID.
Basically, if you are running the same code, same query for all requests on this endpoint, it will never be really unique. You need to add some user specific checks so you can differentiate the output for the current requesting user.
Depending on your use-case and database models, if the data in the table image_links is also created/inserted by some user action you might want additionally save some user ID alongside these values, eg. by extending the table model with another "user_id" column and on insert also add the id of the current user.
You are using some auth decorator #is_logged_in, if you are already handling users in some table then the another user_id column could be a reference to the respective user's primary key. Then, in your example, you would just add additional where user_id = check with the current user's primary key.
As I see in this SQL query:
SELECT mylist, myurl, swatch, parent FROM image_links WHERE status
perhaps you did specify the related user to get its own specific data, try to replace that last "where" with:
WHERE id = (user.id) --> user object
or you could use the AND keyword, something like
WHERE status = (x) AND id = (y)
I am trying to do the following in 3 forms that are processed at once:
1. Save the subject line for an email (this works)
2. Save the email content. (this works)
3. Save the email title (works) and save the relationship to the subject line and to the email content (not working).
I have read through every page I could which had to do with errors. I have tried it a variety of different ways, but they all haven't worked for me.
View.py:
def email_subject_create_view(request):
if request.method == 'POST':
email_subject_form = EmailSubjectForm(request.POST)
email_content_form = EmailContentForm(request.POST)
email_form = EmailForm(request.POST)
if email_subject_form.is_valid() and email_content_form.is_valid() and email_form.is_valid() :
subject = email_subject_form.save(commit=False)
subject.time_created = datetime.datetime.now()
contractor = Contractor.objects.get(user_id=request.user.id)
subject.save()
email_subject_form.save_m2m()
content = email_content_form.save(commit=False)
content.time_created = datetime.datetime.now()
content.save()
email_content_form.save_m2m()
email = email_form.save(commit=False)
email.time_created = datetime.datetime.now()
# this is what I want to do. email.email_core_contents is a M2M field
email.email_subject_lines = subject.id
email.save()
context = {
'email_subject_form': EmailSubjectForm(),
'email_content_form': EmailContentForm(),
'email_form': EmailForm(),
}
return render(request, 'advertise/email_subject_create.html', context)
I have tried:
email.email_subject_lines = EmailSubject.objects.get(pk=subject.id)
email.email_subject_lines.set(subject)
email.email_subject_lines.set(pk=subject.id)
I have also tried it without the .save_m2m() portions of code.
Edit:
Error in the main example:
Direct assignment to the forward side of a many-to-many set is prohibited. Use email_subject_lines.set() instead.
Error in 1 of the 3 set:
Direct assignment to the forward side of a many-to-many set is prohibited. Use email_subject_lines.set() instead.
Error in the 2 of the 3 set:
"<Email: sfeaesfe>" needs to have a value for field "id" before this many-to-many relationship can be used.
Error in the 3 of the 3 set:
"<Email: graegre>" needs to have a value for field "id" before this many-to-many relationship can be used.
Found two solutions. Here they are in case anyone runs into the same problem in the future.
1: Use the add method.
email.email_subject_lines.add(subject)
email.email_core_contents.add(content)
2. Fetch both objects (Email and EmailSubject) from the database again.
email2 = Email.objects.get(id=email.id)
subject2 = EmailSubject.objects.filter(id=subject.id)
email2.email_subject_lines.set(subject2)
email2.save()
Not sure why it works this second way, but not the original way. Note that I had to use filter instead of get in the second expression.
I have a form with a select list.
When the user selects a value of 8888 or 9999 from the award_grant_type select list, I want some of the data that may or may not exist in the form input fields (the user may have entered data into the form text input fields and then selected 8888 or 9999) to be deleted before the form data is commited to the database.
So I have the following model.py code:
.....
DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITH_PROMPT = 8888
DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITHOUT_PROMPT = 9999
.....
AWARD_GRANT_TYPES = (
(SELECT_AWARD_AND_GRANT_TYPE, _('Select Type')),
(AWARD, _('Award')),
(GRANT, _('Grant')),
(TRAVEL_GRANT, _('Travel Grant')),
(OTHER_AWARD, _('Other Award')),
(OTHER_GRANT, _('Other Grant')),
(WRITE_MY_OWN_AWARD_AND_GRANT_TYPE_DESCRIPTION, _('Write my own Type description')), #7777
(DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITH_PROMPT, _('Display only Description with prompt')), #8888
(DISPLAY_ONLY_AWARD_AND_GRANT_DESCRIPTION_WITHOUT_PROMPT, _('Display only Description without prompt')) #9999
)
user = models.ForeignKey(User)
language_version = models.ForeignKey('LanguageVersion')
award_grant_type = models.PositiveIntegerField(choices=AWARD_GRANT_TYPES, default=SELECT_AWARD_AND_GRANT_TYPE, validators=[MinValueValidator(1)])
award_grant_type_description = models.CharField(null=True, blank=True, max_length=250)
award_grant_date = models.DateField(null=True, blank=True)
award_grant_description = models.TextField(null=False, blank=False, max_length=5000)
Here is my forms.py clean code that should remove the award_grant_type_description and award_grant_date fields when the user has selected 8888 or 9999 from the select list award_grant_type before being committed to the db:
def clean(self):
cd_agdf = super(AwardGrantDetailsForm, self).clean()
if 'award_grant_type' in cd_agdf:
if cd_agdf['award_grant_type'] == '':
self._errors['award_grant_type'] = self.error_class([_("You must select a Type.")])
elif cd_agdf['award_grant_type'] == 8888 or cd_agdf['award_grant_type'] == 9999:
# remove the entered values when the award grant type only requires minimum data.
self.cleaned_data.pop('award_grant_type_description', None)
self.cleaned_data.pop('award_grant_date', None)
else:
....
return cd_agdf
Can anyone point out what I have done incorrectly? The award_grant_type_description and award_grant_date are not removed before the form data is committed to the db.
EDIT / UPDATE
This issue only occurs when the existing record is updated. A new record removes the data as required before the form is saved to the db. When an existing record has a date field as part of the db record and the award_grant_type is changed from say 1 to 8888 or 9999, then the award_grant_date is NOT removed from the db. I cannot figure out why.
2nd EDIT
I have posted a related thread here.
Try to change self.cleaned_data to cd_agdf. Dictionary, that method clean returns, will be used as cleaned_data. You popped items from self.cleaned_data, but returned not changed cd_ahdf. This is described here (see last step starting with "The form's subclass...").
Answering to the edit only: there is a fight over what the data means.
Form validation happens first, its purpose is to parse the submitted data, clean it up and point any error in the data. The result of this step should be the cleaned up, canonical form of the data.
Then action: if valid, form is acted upon. You interpret that data to take appropriate action. Here, writing it to a database.
Your two steps disagree on the meaning of data. Validation removes award_grant_type_description and award_grant_date from the final data to mean “blank out those fields”. Then, the action interprets missing data as “leave that field as it is” (that's what the default ModelForm.save() does).
Your choice: either conform to ModelForm's convention and set the fields to None instead of removing them, or override your form's save() so it interprets missing data as “remove from object”. Unless I had a good reason to alter ModelForm's semantics, I'd go with setting the fields to None.
I finally figured this issue out.
When I wrap the values in quotation marks, the issue is resolved.
Here is the example code I used that works:
elif cd_agdf['award_grant_type'] == '8888' or cd_agdf['award_grant_type'] == '9999':
I hope that this will help someone.
instead of self.cleaned_data.pop()
do cd_agdf.pop() as you are assigning cd_agdf to superclass
class PersonSite(models.Model):
vps_id = models.AutoField(primary_key=True)
person = models.ForeignKey(CanonPerson, db_column='p_id',null=True)
site = models.ForeignKey(CanonSite, db_column='s_id',null=True)
person_sites = PersonSite.objects.filter(person=cp)
for person_site in person_sites:
if person_site and person_site.site_id and person_site.site.s_id:
# crashes for some records
We have a problem with the data, where PersonSite may point to a site that no longer exists.
In the debugger I can see that person_site.site_id has a value of 5579, however that id doesn't exist in the database:
select * from tbl_vpd_sites where s_id = 5579
Hence person_site.site_id is not null, yet just accessing person_site.site within the conditional crashes the app with the message:
DoesNotExist: CanonSite matching query does not exist.
This is a very difficult situation, I can't even check for this case to bypass it.
PersonSite.site has null=True, so it makes sense that you have to check if a object exists before accessing it.
In stead of doing all those checks if person_site and person_site.site_id and person_site.site.s_id: you can just query the db and filter the empty sites out.
person_sites = PersonSite.objects.filter(person=cp).filter(site__isnull=False)
This will return only the PersonSite objects where site IS NOT NULL and therefore have a pk.
I have a data structure that declares relationships like this (pseudocode):
class User:
...
class Rating:
rater = User
post = Post
class Post:
ratings = hasmany(Rating)
page_id = ...
I'm building a website using these models, and I'd I'm lazy, so I pass my template the current logged in User, and a bunch of Posts on the current page. My page needs to know what rating the logged in user gave to each Post, so I use SQLAlchemy's one-instance-per-session feature:
posts = session.query(Post).filter(Post.page_id==current_pageid)
ratings = session.query(Post, Rating)\
.filter(Rating.rater==user.id)\
.filter(Post.page_id==current_pageid)
for post in posts:
post.user_rating = None # default value
for post, rating in ratings:
post.user_rating = rating
Then I pass my template the posts list. Is this ugly awful practice? Can I do it some better way?
What you are doing is good enough, except that your query is lacking a WHERE clause between the Post and Rating:
# ...
.filter(Post.id==Rating.post_id)\
But you can also get the result in one query:
qry = (session.query(Post, Rating).
outerjoin(Rating, and_(Post.id==Rating.post_id, Rating.user_id==user.id)).
filter(Post.page_id==current_pageid)
)
res = qry.all() # you can return *res* already to a view, but to get to your results, do below as well:
for post, rating in res:
post.user_rating = rating
posts = [post for post, rating in res]
return posts
Note that in your case posts is not really a list, but a query, and if you iterate over it second time, you might lose the user_rating attribute. You should be cautious returning session-bound objects like query to a view. It is safer to return lists like in the same code above. To fix your code, just add .all() to the query:
posts = session.query(Post).filter(Post.page_id==current_pageid).all()
Yes, it's bad practice. And it even might (in theory) beat you at some moment, e.g. when you query from the same session without clearing it for some post SQLAlchemy will return you the same cached object with already filled rating for some user unrelated to the current context. In practice it will work find in most cases.
Why not just pass a list of (post, rating) pairs to template? Most modern template engines available for Python can iterate over the list of pairs.
BTW, you can fetch both posts and ratings with single query (rating object will be None for OUTER JOIN when it's missing):
session.query(Post, Rating).select_from(Post)\
.outerjoin(Rating, (Post.id==Rating.post_id) & (Rating.rater==…))\
.filter(Post.page_id==…)