I have created a simple database for storing points for each user that has a unique user id (18-digit long):
CREATE TABLE public.user_points (
id SERIAL,
user_id BIGINT UNIQUE,
points INTEGER
)
I want to be able to create and insert data to the specific user id by giving the user id e.g doing:
# ------------------------------------------------------------------------------- #
class User(Model):
user_id = IntegerField(column_name='user_id')
points = IntegerField(column_name='points')
class Meta:
database = postgres_pool
db_table = "user_points"
#classmethod
def add_points(cls, user_id):
try:
return (cls
.insert(user_id=user_id, points=10)
.on_conflict(
conflict_target=[User.user_id],
preserve=(User.user_id),
update={User.points: User.points + 10})
.execute())
except peewee.IntegrityError as err:
logger.exception(f"{type(err).__name__} at line {err.__traceback__.tb_lineno} of {__file__}, {url}: {err}")
postgres_pool.rollback()
return None
which works fine!
And my issue is now that when I give a user id I want it to return if there is a user by that user id in the database and how many points the user has by doing
#classmethod
def get_user_points(cls, user_id):
try:
return cls.select(cls.points).where(98468376409550848 == User.user_id).get()
except User.DoesNotExist:
print('Doesnt exists)')
return None
except peewee.IntegrityError as err:
logger.exception(f"{type(err).__name__} at line {err.__traceback__.tb_lineno} of {__file__}, {err}")
postgres_pool.rollback()
return None
and using cls.select(cls.points).where(98468376409550848 == User.user_id).get() returns only None but it should return 70 - I wonder why and how can I be able to get points based on the user?
You can use returning to get the ID and points:
iq = (cls
.insert(user_id=user_id, points=10)
.on_conflict(
conflict_target=[User.user_id],
preserve=(User.user_id),
update={User.points: User.points + 10})
.returning(User))
return iq.execute()[0] # Return the user.
You shouldn't need to catch the integrity error above because you are explicitly telling PG what to do on conflict w/the user id.
To simply retrieve the user points by ID:
return User.select(User.points).where(User.user_id == user_id).scalar()
Related
How can i update the same group if the name of group user wants to create matches with already created group? If i want to update instead of showing error where should i work on? Is it on validate function or create function?
Here is my serializer
class DeviceGroupSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
devices = DeviceIdSerializer(many=True)
class Meta:
model = DeviceGroup
fields = ['id','name', 'devices',]
def validate(self, data):
errors = {}
try:
name = data['name']
if not bool(name):
#empty or null
errors['name'] = 'Name cannot be empty'
except KeyError:
if not (self.instance and bool(self.instance.name)):
errors['name'] = 'Name is required'
if len(data.get('devices', [])) == 0:
errors['devices'] = 'Device(s) should be specified.'
if bool(errors):
raise serializers.ValidationError(errors)
return data
def create(self, validated_data):
# for create - there is always name; we have already checked that in validation
# TODO Further check for group-name clash - if yes, update the same group
owner = validated_data['owner']
name = validated_data['name']
group = DeviceGroup.objects.create(owner=owner, name=name)
tokens = [d['token'] for d in validated_data['devices'] ]
BaseDevice.objects.filter(token__in=tokens, owner=owner).update(group=group)
return group
def update(self, instance, validated_data):
# for update - there may or may not be name
# if it does, it refers rename
owner = validated_data['owner']
name = validated_data.get('name', None)
if not name is None:
instance.update(name=name)
tokens = [d['token'] for d in validated_data['devices'] ]
BaseDevice.objects.filter(token__in=tokens, owner=owner).update(group=instance)
return instance
You want update_or_create():
A convenience method for updating an object with the given kwargs,
creating a new one if necessary. The defaults is a dictionary of
(field, value) pairs used to update the object.
Based on what you've shared, this would look something like the following, assuming you want to update the owner on DeviceGroup, if a DeviceGroup with the given name already exists:
def create(self, validated_data):
# for create - there is always name; we have already checked that in validation
# TODO Further check for group-name clash - if yes, update the same group
owner = validated_data['owner']
name = validated_data['name']
# created is a boolean telling us if a new DeviceGroup was created
group, created = DeviceGroup.objects.update_or_create(name=name, defaults={'owner': owner})
tokens = [d['token'] for d in validated_data['devices'] ]
BaseDevice.objects.filter(token__in=tokens, owner=owner).update(group=group)
return group
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
...
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.
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
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.