Deform Inter-Field Validation not highlighting field - python

I followed this example, but I modified it a bit to suit my project
This is what I have:
class AgentFormValidation(object):
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self, form, value):
number = value['identity_information']['number']
print validateID(number)
type = value['identity_information']['type']
q = sqlahelper.get_session().query(Agents.id_number).filter(Agents.id_number == number).first()
if type == "IDNumber":
if not validateID(number):
if q:
exc = colander.Invalid(form["identity_information"], "ID Number %s already exists in Database" % number)
exc.number = "ID Number already exists "
raise exc
else:
exc = colander.Invalid(form["identity_information"], "ID Number %s is not a valid SA ID Number" % number)
exc.number = "Invalid ID number"
raise exc
elif type == "Passport":
if q:
exc = colander.Invalid(form["identity_information"], "Passport number %s already exists in Database" % number)
exc.number = "Passport number already exists"
raise exc
def gen_agent_schema_form(self):
_but = ('create agent',)
_title = "Create Agent"
if not self.context.__is_new__:
_but = ('update agent',)
_title = "Agent Details"
deals = []
if self.context.ou:
deals = [(deal.id, str(deal)) for deal in self.context.ou[0].org_deals]
schema = Agent(validator=AgentFormValidation(self.context, self.request), title=_title).bind(deals=deals)
form = Form(schema, buttons=_but)
return schema, form
The validation works just fine. It just doesn't want to highlight the element.
When I replace:
exc.number = "ID Number already exists"
with
exc['number'] = "ID Number already exists"
It does highlight, but it Highlights the very first element on the form, which is first_name, which is also wrong.
I feel like I'm missing something small.
UPDATE
So I played around a little, when I do:
exc = colander.Invalid(form, "ID Number %s already exists in Database" % number)
exc["identity_information"] = "ID Number already exists "
raise exc
I get an alert message box(not js alert) above the correct field:
Instead of this, I need the field to highlight as in the example above.

In your custom validator you always pass the form as the first parameter to colander.Invalid(). This way you add validation messages to the top of the form, but you do not trigger highlighting of schema nodes / elements. Start using simple validators working on a single schema node/element.
exc = colander.Invalid(form["identity_information"], "ID Number %s already exists in Database" % number)
Anyway, I don't see a clear requirement for inter-field validation. Instead of applying a complex class-based validator on the Agent Schema you could use available colander validators or create custom validators on each Agent Schema node, thus keeping validators as simple as possible. Your current validator implementation cares about too much for too many use cases.
I assumed you have two different use cases - registration and some another agent-related task. By inventing different schemas I can keep validation specific to schema nodes and use case. Uniqueness of ID may only be important while creating stuff with an add/create form, in an update form users may not able to change all these values but a limited set of personal information.
Main idea is usally apply schema node validators to get validation messages at schema nodes. In special cases apply form level validators (agent registration).
Your use case illustrates the domain of form validation. A class that validates form inputs against schemas and handles persistence-related topics (primary key uniqueness) is doing too much. Both should be encapsulated to evolve separately. Your business rules will change in time, the database table will always require uniqueness for primary keys.
Schemas
import colander
class AgentRegistration(colander.Schema):
"""schema for agent registration with email validation
Note the form validator is invoked only if none of the individual field validators raise an error.
"""
first_name = colander.SchemaNode(colander.String())
number = colander.SchemaNode(colander.Integer(), validator=can_register_agent)
email = colander.SchemaNode(colander.String(), validator=colander.Email())
verify_email = colander.SchemaNode(colander.String(), validator=colander.Email())
validator = verify_email_validator
class AgentDeals(colander.Schema):
"schema for managing agent deals"
first_name = colander.SchemaNode(colander.String())
number = colander.SchemaNode(colander.Integer(), validator=validateID)
email = colander.SchemaNode(colander.String(), validator=colander.Email())
Schema Validators
def agent_unique(node, value):
"validate uniqueness of value in database table Agents"
if sqlahelper.get_session().query(Agents.id_number).filter(Agents.id_number == value).first()
raise Invalid(node,
'ID Number %r is already given to another agent. Please change it' % value)
def valid_SA_ID(node, value):
"validates SA ID Number - just a copy of your requirement calling your custom function"
if not validateID(value):
raise Invalid(node,
'SA ID Number %r is not valid.' % value)
def can_register_agent(node, value):
"ensure Agent ID Number is a valid and not already existing in database"
valid_SA_ID(node, value)
agent_unique(node, value)
def verify_email_validator(form, values):
"""schema level validator with access to all values
validates emails are same
validation messages are displayed on top of form"""
if values['email'] != values['verify_email']:
raise colander.Invalid(form, 'Email values do not match.')
Form
class AgentRegistrationView(object)
def __init__(self, request):
"""Set some common variables needed for each view.
"""
self.request = request
self.context = request.context
#view_config(route_name='add_agent', permission='admin', renderer='add_agent.mako')
def add_agent(self):
"""return form to create new agents
may be we do not need to bind any deals data here"""
schema = AgentRegistration()
form = deform.Form(schema, action=self.request.route_url('add_agent'), buttons=('Add Agent','Cancel'))
class AgentDealsView(object)
def __init__(self, request):
"""Set some common variables needed for each view.
"""
self.request = request
self.context = request.context
def get_deals(self)
"""return deals to be bound to Agent Schema"""
if self.context.ou:
return [(deal.id, str(deal)) for deal in self.context.ou[0].org_deals]
#view_config(route_name='edit_agent' permission='edit', renderer='edit_agent.mako')
def edit_agent(self):
# we bind deals data to form
schema = AgentDeals().bind(deals=self.get_deals())
form = deform.Form(schema, action=self.request.route_url('edit_agent'), buttons=('Save','Cancel'))
References
Extending colander - Defining a new validator
Colander Schema binding - An example
Deform/colander with access to all nodes
Colander's Schema validation or Deform's form validation?
Besides that you may be interested in ColanderAlchemy, but it adds another level of abstraction. Actually, I do not recommend it to you.

Related

Wagtail/Django: Query filter to return only the pages the user has acess/permissions?

I'm going through the documentation at: http://docs.wagtail.io/en/v2.7.1/reference/pages/queryset_reference.html.
Is there a filter to return only the pages the user has access to? I can only see public() and not_public().
I have some pages which privacy is set to Private (accessible to users in specific groups). And would like to exclude them from the query results.
There is no such filter in PageQuerySet. You can however create your own QuerySet that adds an authorized filter and use that. The following code comes from the Joyous events EventQuerySet and is based upon PageQuerySet.public_q and BaseViewRestriction.accept_request. It gets all the restrictions that could apply, excludes the ones that the user passes, and then filters out the pages with the remaining restrictions.
from wagtail.core.query import PageQuerySet
from wagtail.core.models import Page, PageManager, PageViewRestriction
class MyQuerySet(PageQuerySet):
def authorized_q(self, request):
PASSWORD = PageViewRestriction.PASSWORD
LOGIN = PageViewRestriction.LOGIN
GROUPS = PageViewRestriction.GROUPS
KEY = PageViewRestriction.passed_view_restrictions_session_key
restrictions = PageViewRestriction.objects.all()
passed = request.session.get(KEY, [])
if passed:
restrictions = restrictions.exclude(id__in=passed,
restriction_type=PASSWORD)
if request.user.is_authenticated:
restrictions = restrictions.exclude(restriction_type=LOGIN)
if request.user.is_superuser:
restrictions = restrictions.exclude(restriction_type=GROUPS)
else:
membership = request.user.groups.all()
if membership:
restrictions = restrictions.exclude(groups__in=membership,
restriction_type=GROUPS)
q = models.Q()
for restriction in restrictions:
q &= ~self.descendant_of_q(restriction.page, inclusive=True)
return q
def authorized(self, request):
self.request = request
if request is None:
return self
else:
return self.filter(self.authorized_q(request))
You could then set this to be your model's default QuerySet.
class MyPage(Page):
objects = PageManager.from_queryset(MyQuerySet)()
Then when filtering your MyPage objects you can say MyPage.objects.live().authorized(request).all()
Hope that is helpful. May contain bugs.

Python MongoDB dynamic query building with multiple conditions

I am building an Open Source Project, Python MongoDB ORM (for Flask especially) using flask_pymongo and I am kind of stuck at building dynamic conditions.
Below code is what I have written in corresponding files
Model.py
from app.database import Database
class Model:
conditions = {"and":[], "or":[], "in":[]}
operators = {
"!=": "$ne",
"<": "$lt",
">": "$gt",
"<=": "$lte",
">=": "$gte",
"in": "$in",
"not in":"$nin",
"and": "$and",
"or": "$or"
}
def __init__(self):
# collection property from User class
# Database class takes collection to fire MongoDB queries
self.db = Database(self.collection)
def where(self, field, operator, value=None):
if value is None:
# to enable Model.where("first_name", "John")
value = operator
operator = "="
self._handle_condition("and", field, operator, value)
# to enable Model.where().where_or() and etc
return self
def where_or(self, field, operator, value=None):
if value is None:
# to enable Model.where("first_name", "John")
value = operator
operator = "="
self._handle_condition("or", field, operator, value)
# to enable Model.where().where_or() and etc
return self
def _handle_condition(self, type, field, operator, value):
self.conditions[type].append({"field":field, "operator":operator, value:value})
def get(self):
filetrs = {}
for type in self.conditions:
filetrs[self.operators[type]] = []
for condition in self.conditions[type]:
if condition["operator"] == "=":
filter = {condition["field"]:condition["value"]}
else:
filter = {condition["field"]:{self.operators[condition["operator"]]:condition["value"]}}
filetrs[self.operators[type]].append(filter)
return self.db.find(filters)
User.py
from app.Model import Model
class UserModel(Model):
# MongoDB collection name
collection = "users"
def __init__(self):
Model.__init__(self)
User = UserModel()
What I want to achieve is, from UserController.py where User.py is imported and used like the mentioned code.
Where multiple conditions are being added using where and where_or Model methods, get methods is parsing all the conditions and passing as filter to find method
UserController.py
from app.User import User
class UserController:
def index(self):
# Should return all the users where _id is not blank or their first_name is equal to John
return User.where("_id", "!=", "").where_or("first_name", "John").get()
The problem is this is not working at it should be, it seems working fine for any one condition, where or where_or but when I try to add multiple where and where_or conditions it is not working.
Your help is really appreciated.
PS: This question seems to have lots of code but to make you understand the complete scenario I had to, please feel free to comment if you still need any clarifications.
Eagerly looking forward.

django form custom clean gives keyerror

I am using a custom clean on a form I made. But then some weird things happen
If my input data is valid. it works fine. However when it is not correct, I get a KeyError at key_ID in the clean method.
Also, if I add print statements in the clean_key_ID and clean_verification_code, these don't show up in my console.
My form:
class ApiForm(forms.Form):
key_ID = forms.CharField(min_length=7, max_length=7, required=True)
verification_code = forms.CharField(
min_length=64,
max_length=64,
required=True,
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super(ApiForm, self).__init__(*args, **kwargs)
def clean_key_ID(self):
data = self.cleaned_data['key_ID']
try:
int(data)
except ValueError:
raise forms.ValidationError("Should contain 7 numbers")
return data
def clean_verification_code(self):
data = self.cleaned_data['verification_code']
if not re.match("^[A-Za-z0-9]*$", data):
raise forms.ValidationError(
"Should only contain 64 letters and numbers"
)
return data
def clean(self):
key = self.cleaned_data['key_ID']
vcode = self.cleaned_data['verification_code']
if Api.objects.filter(key=key, vcode=vcode, user=self.user).exists():
raise forms.ValidationError(
"This key has already been entered, try to update it"
)
#connect with api and validate key
api = api_connect()
auth = api.auth(keyID=key, vCode=vcode)
try:
keyinfo = auth.account.APIKeyInfo()
except RuntimeError:
raise forms.ValidationError("Invallid data, cannot connect to api")
in my view I get my form in the normal way:
view:
api_form = ApiForm(request.POST or None, user=request.user)
if request.POST and api_form.is_valid():
#do things with form
I am lost and have no clue where this error is coming from. My form is rendered in the normal way {{api_form}}.
I have already tried removing the underscores in my field names. But the error remains
If key_ID is invalid, then it will not be in self.cleaned_data in your clean method. You have to change your clean method to handle this, for example:
def clean(self):
key = self.cleaned_data.get('key_ID')
if not key:
# early return
return
# rest of method as before
...

Django request not available in form clean

I can't access request from the clean field of a ModelForm. I want to check that the request user is not trying to attend too many events.
I've tried this fix: Django: Accessing request in forms.py clean function
...among other things but am still struggling.
My form is this:
class EventEditForm(forms.ModelForm):
class Meta:
model = Event
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(EventEditForm, self).__init__(*args, **kwargs)
name = forms.CharField(max_length=1024,
initial="Give short, descriptive name",
widget=forms.TextInput(attrs={'onfocus':'if(this.value=="Give short, descriptive name") this.value="";'}))
location = forms.CharField(max_length=1024,
initial="Be specific, give online map link",
widget=forms.TextInput(attrs={'onfocus':'if(this.value=="Be specific, give online map link") this.value="";'}))
dateTimeOptions = {
'format': 'dd/mm/yyyy HH:ii P',
'autoclose': 'true',
'showMeridian': 'true',
}
date = forms.DateTimeField(label="Date and time",widget=DateTimeWidget(options=dateTimeOptions,
attrs={'id':"date-time"}))
host_act = forms.CharField(max_length=1024,
initial="What act will you bring, if any?", required=False,
widget=forms.TextInput(attrs={'onfocus':'if(this.value=="What act will you bring, if any?") this.value="";'}))
description = forms.CharField(required=False, max_length=10240,
initial="Give some details. Any activities prepared?",
widget=forms.Textarea(attrs={'onfocus':'if(this.value=="Give some details. Any activities prepared?") this.value="";'}))
def clean_location(self):
cd = self.cleaned_data
location = cd.get('location')
if location == "Be specific, give online map link" or '':
raise forms.ValidationError("Please enter a location")
return location
def clean_name(self):
cd = self.cleaned_data
name = cd.get('name')
other_names = Event.objects.proposed(datetime.now)
if name == "Give short, descriptive name" or '':
raise forms.ValidationError("Please enter a name")
return name
def clean(self):
"""
Check that there is not another event on at
the same time and place. Then check that user has not committed
to another event on the same date, even somewhere else.
"""
cleaned_data = super(EventEditForm, self).clean()
event_start_estimate = cleaned_data.get("date") - timedelta(hours=3)
event_end_estimate = event_start_estimate + timedelta(hours=7)
location = cleaned_data.get("location")
events_on_date = Event.objects.\
filter(date__range=[event_start_estimate,event_end_estimate])
events_at_location_on_date = events_on_date.filter(location=location)
print events_at_location_on_date
# Check event clash is not this event clashing with itself
try:
events_with_same_date_and_location_id = events_at_location_on_date[0].id
except:
pass
this_event_id = self.instance.id
try:
if events_with_same_date_and_location_id == this_event_id:
events_at_location_on_date = False
except:
pass
if events_at_location_on_date:
raise forms.ValidationError("There is already an event on \
this date. Please join this event or pick another \
date and time. Note: You may have accidentally failed to \
change the date with the picker.")
print self.request
user = self.request
print user
events_on_date_user_has_commit = events_on_date.filter(host__exact=user)
if events_on_date_user_has_commit:
raise forms.ValidationError("You are already committed to an event\
on this date.")
return cleaned_data
My view is:
class EventEditView(UpdateView):
template_name = "edit_event.html"
model=Event
pk_url_kwarg='event_id'
form_class = EventEditForm
You cannot access the request in a form. You need to pass it in from the view in some way. If you're using a FormView generic view, the answer you have linked to is a great way to do it. Looks like you are already expecting the request to be sent in:
self.request = kwargs.pop('request', None)
If you are using a function based view, or a non FormView classed based view, just pass in the request as a keyword argument when you initialize the form. For example:
form = EventEditForm(request.POST, request=request) # or self.request for a class based view

What is the proper way to delineate modules and classes in Python?

I am new to Python, and I'm starting to learn the basics of the code structure. I've got a basic app that I'm working on up on my Github.
For my simple app, I'm create a basic "Evernote-like" service which allows the user to create and edit a list of notes. In the early design, I have a Note object and a Notepad object, which is effectively a list of notes. Presently, I have the following file structure:
Notes.py
|
|------ Notepad (class)
|------ Note (class)
From my current understanding and implementation, this translates into the "Notes" module having a Notepad class and Note class, so when I do an import, I'm saying "from Notes import Notepad / from Notes import Note".
Is this the right approach? I feel, out of Java habit, that I should have a folder for Notes and the two classes as individual files.
My goal here is to understand what the best practice is.
As long as the classes are rather small put them into one file.
You can still move them later, if necessary.
Actually, it is rather common for larger projects to have a rather deep hierarchy but expose a more flat one to the user. So if you move things later but would like still have notes.Note even though the class Note moved deeper, it would be simple to just import note.path.to.module.Note into notes and the user can get it from there. You don't have to do that but you can. So even if you change your mind later but would like to keep the API, no problem.
I've been working in a similar application myself. I can't say this is the best possible approach, but it served me well. The classes are meant to interact with the database (context) when the user makes a request (http request, this is a webapp).
# -*- coding: utf-8 -*-
import json
import datetime
class Note ():
"""A note. This class is part of the data model and is instantiated every
time there access to the database"""
def __init__(self, noteid = 0, note = "", date = datetime.datetime.now(), context = None):
self.id = noteid
self.note = note
self.date = date
self.ctx = context #context holds the db connection and some globals
def get(self):
"""Get the current object from the database. This function needs the
instance to have an id"""
if id == 0:
raise self.ctx.ApplicationError(404, ("No note with id 0 exists"))
cursor = self.ctx.db.conn.cursor()
cursor.execute("select note, date from %s.notes where id=%s" %
(self.ctx.db.DB_NAME, str(self.id)))
data = cursor.fetchone()
if not data:
raise self.ctx.ApplicationError(404, ("No note with id "
+ self.id + " was found"))
self.note = data[0]
self.date = data[1]
return self
def insert(self, user):
"""This function inserts the object to the database. It can be an empty
note. User must be authenticated to add notes (authentication handled
elsewhere)"""
cursor = self.ctx.db.conn.cursor()
query = ("insert into %s.notes (note, owner) values ('%s', '%s')" %
(self.ctx.db.DB_NAME, str(self.note), str(user['id'])))
cursor.execute(query)
return self
def put(self):
"""Modify the current note in the database"""
cursor = self.ctx.db.conn.cursor()
query = ("update %s.notes set note = '%s' where id = %s" %
(self.ctx.db.DB_NAME, str(self.note), str(self.id)))
cursor.execute(query)
return self
def delete(self):
"""Delete the current note, by id"""
if self.id == 0:
raise self.ctx.ApplicationError(404, "No note with id 0 exists")
cursor = self.ctx.db.conn.cursor()
query = ("delete from %s.notes where id = %s" %
(self.ctx.db.DB_NAME, str(self.id)))
cursor.execute(query)
def toJson(self):
"""Returns a json string of the note object's data attributes"""
return json.dumps(self.toDict())
def toDict(self):
"""Returns a dict of the note object's data attributes"""
return {
"id" : self.id,
"note" : self.note,
"date" : self.date.strftime("%Y-%m-%d %H:%M:%S")
}
class NotesCollection():
"""This class handles the notes as a collection"""
collection = []
def get(self, user, context):
"""Populate the collection object and return it"""
cursor = context.db.conn.cursor()
cursor.execute("select id, note, date from %s.notes where owner=%s" %
(context.db.DB_NAME, str(user["id"])))
note = cursor.fetchone()
while note:
self.collection.append(Note(note[0], note[1],note[2]))
note = cursor.fetchone()
return self
def toJson(self):
"""Return a json string of the current collection"""
return json.dumps([note.toDict() for note in self.collection])
I personally use python as a "get it done" language, and don't bother myself with details. This shows in the code above. However one piece of advice: There are no private variables nor methods in python, so don't bother trying to create them. Make your life easier, code fast, get it done
Usage example:
class NotesCollection(BaseHandler):
#tornado.web.authenticated
def get(self):
"""Retrieve all notes from the current user and return a json object"""
allNotes = Note.NotesCollection().get(self.get_current_user(), settings["context"])
json = allNotes.toJson()
self.write(json)
#protected
#tornado.web.authenticated
def post(self):
"""Handles all post requests to /notes"""
requestType = self.get_argument("type", "POST")
ctx = settings["context"]
if requestType == "POST":
Note.Note(note = self.get_argument("note", ""),
context = ctx).insert(self.get_current_user())
elif requestType == "DELETE":
Note.Note(id = self.get_argument("id"), context = ctx).delete()
elif requestType == "PUT":
Note.Note(id = self.get_argument("id"),
note = self.get_argument("note"),
context = ctx).put()
else:
raise ApplicationError(405, "Method not allowed")
By using decorators I'm getting user authentication and error handling out of the main code. This makes it clearer and easier to mantain.

Categories