How to set limit on models in Django - python

I have a little problem. I want to set the limit for users in my app. For example: The new user after register and login to his account can add only one record. After added if this same user want to add next record, the app will return the alert "You are permited to add only one record". How to solve it?

You need to have a way to remember if the user used their limit or not. I know 2 options here:
Add a bool/int field to the user that remembers if/how many records that user created.
This is the best tutorial I have found so far regarding extending user model with some additional data:
https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html
Alternatively, you could create a foreign key in a forementioned "record" to the user (e.g. called "creator"). The reverse link from user to the record would give you the records created by this user.
No matter which solution works better for you, you'll need to check the conditions in the creation view and throw an error if the limit would be exceeded (e.g. PermissionDenied)

You can build a model similar to this that defines the limit per user and validating the inserted data using a classmethod:
class LimitedRecord(models.Model):
limits = 5
user = models.ForeignKey(User)
data = models.CharField(max_length=128)
#classmethod
def add_record(cls, user, data):
if not cls.can_add(user):
raise Exception('Not Allowed')
instance = cls.objects.create(user=user, data=data)
return instance
#classmethod
def can_add(cls, user):
return cls.objects.filter(user=user).count() <= cls.limits:
I separated the login here into two methods, but sure you can combine them in the same unit that adds the record.

Add two fields to your User model:
class User(models.Model):
n_records_permitted = models.PositiveIntegerField(default=1)
n_records_added = models.PositiveIntegerField(default=0)
Now either don't display the relevant form(s) for Users that have user.n_records_added >= user.n_records_permitted or include this check in the form's validation.
And of increase both counters as appropriate.

Related

Django. Check `unique_together` only if both Model fields were provided for create and update calls

I need to check unique_together only in case both fields were supplied to admin creation Form or via API request for both create and update calls. In case this happens and fields are not unique_together I need to propagate Exception to django-admin creation Form and RestFramework's Serializer/ViewSet.
Here's a simplified example of my model:
class MyModel(models.Model):
time = models.TimeField(null=True, blank=True)
date = models.DateField(null=True, blank=True)
some_other_field = models.BooleanField(default=False)
...
As you can see at the level of model both time and date are not required and are nullable.
Application logic on the other hand requires at least one of those fields to be provided and if both are supplied – this pair should be unique for entire table to avoid duplication of datetime composition.
This model is accessed through these nodes: django-admin and DjangoRestFramework's endpoint using ViewSet.
Creation of new record does not seem to be a problem from API perspective, I can override validate method of Serializer:
def validate(self, data):
if not data.get('time') and not data.get('date'):
raise serializers.ValidationError(
"At least one of this fields is required: time, date"
)
if OpenTable.objects.filter(
time=data.get('time'),
date=data.get('date')).exists():
raise serializers.ValidationError('Duplicates for date or datetime not allowed')
return data
But this becomes a problem when I receive PUT or PATCH request, because then despite this record being the only one in table I raise ValidationError precisely because there is a record with this date-time pair.
Overriding validate method also does not solve problem of preventing creation of such a pair from Django-Admin.
At the moment the closest I got to validating this is using save method of MyModel, but I can't understand how exactly to handle this case there to comply with create/update flow.
For Django Rest Framework validation, you can use self.instance check to use different validation flows for creation and modification of an object;
def validate(self, data):
if self.instance:
# Modifiying an existing instance, run validations accordingly
else:
# Creading a new instance, run validations accordingly
As for admin site, you can use Django's ModelForm structure and their validations to enforce validtion for admin site. You'll need to set you admin site to use your custom form for MyModel

App Engine Query Users

I have the User model in my datastore which contains some attributes:
I need to query all users filtering by the company attribute.
So, as I would normally do, I do this:
from webapp2_extras.appengine.auth.models import User
employees = User.query().filter(User.company == self.company_name).fetch()
This gives me:
AttributeError: type object 'User' has no attribute 'company'
And when I do:
employees = User.query().filter().fetch()
It gives me no error and shows the list with all the Users.
How do I query by field? Thanks
Your question is a bit misdirected. You ask how to query by field, which you are already doing with correct syntax. The problem, as Jeff O'Neill noted, is your User model does not have that company field, so your query-by-field attempt results in an error. (Here is some ndb documentation that you should definitely peruse and bookmark if you haven't already.) There are three ways to remedy your missing-field problem:
Subclass the User model, as Jeff shows in his answer. This is quick and simple, and may be the best solution for what you want.
Create your own User model, completely separate from the webapp2 one. This is probably overkill for what you want, just judging from your question, because you would have to write most of your own authentication code that the webapp2 user already handles for you.
Create a new model that contains extra user information, and has a key property containing the corresponding user's key. That would look like this:
class UserProfile(ndb.Expando):
user_key = ndb.KeyProperty(kind='User', required=True)
company = ndb.StringProperty()
# other possibilities: profile pic? address? etc.
with queries like this:
from models.user_profile import UserProfile
from webapp2_extras.appengine.auth.models import User
from google.appengine.ext import ndb
# get the employee keys
employee_keys = UserProfile.query(UserProfile.company == company_name).fetch(keys_only=True)
# get the actual user objects
employees = ndb.get_multi(employee_keys)
What this solution does is it separates your User model that you use for authentication (webapp2's User) from the model that holds extra user information (UserProfile). If you want to store profile pictures or other relatively large amounts of data for each user, you may find this solution works best for you.
Note: you can put your filter criteria in the .query() parentheses to simplify things (I find I rarely use the .filter() method):
# long way
employees = User.query().filter(User.company == self.company_name).fetch()
# shorter way
employees = User.query(User.company == self.company_name).fetch()
You've imported a User class defined by webapp2. This User class does not have an attribute called company so that is why you are getting the error from User.company.
You probably want to do create your own User model by subclassing the one provided by webapp2:
from webapp2_extras.appengine.auth.models import User as Webapp2_User
class User(Webapp2_User):
company = ndb.StringProperty()
Then your query should work.
One caveat, I've never used webapp2_extras.appengine.auth.models so I don't know what that is exactly.

Multiple User type with Django

Using Django we have two types of Users (Teachers and Students) with common fields and uncommon fields
In our wizard we first POST the common fields to /users with an extra type_field
Every operation after this should be able to figure out which model (Teacher or Student) it needs to use.
We are thinking of making two models ( Teacher and Student ) with an one-to-one field.
But how do we hookup the type_field to the right Model on every operation?
You dont have to go for an extra field since you are already having two different classes for students and teachers. A simple approach may looks like below.
from django.contrib.auth.models import User
class Teacher(User):
extra_field_1 = models.Fieldtype()
...
...
class Student(User):
extra_field_1 = models.Fieldtype()
...
...
You can provide both type of users same registration form and upon clicking next take them to next page based on the value of I am a teacher/student field. In that case I suggest you to use atomic blocks if you dont want to save data in case registration procedure fails at some point or user have selected a wrong choice and they want to go back. By this approach each inherited models have username, first_name, last_name and email that you dont have to insert any of these to Teacher or student model.
Then you have to create forms for each model. You may use modelform A much better approach will be using class based views since that reduce a lot of code and stick to dry principles.
You may use something like:
class Person(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
# more common fields
class Student(Person):
specific_field_to_student = models.CharField(max_length=255)
# more fields
class Teacher(Person):
specific_field_to_teacher = models.CharField(max_length=255)
# more fields
In your database you will have 3 tables (yourapp_person, yourapp_student and yourapp_teacher). Now, if type_field value is student, you will use Student model to create user, if it is teacher, you will use Teacher model.
Note: You may need to make Person model above a subclass of the built-in User model.
Edit:
I have edited the model above to take into account the requirements in the comments below.
Now, to retrieve user by id, you can use the following code in your view:
user = Person.objects.get(id=id) # id is the view param
if hasattr(user, 'student'):
print("Student")
else: # hasattr(user, 'teacher')
print("Teacher")

How to model a 'Like' mechanism via ndb?

We are about to introduce a social aspect into our app, where users can like each others events.
Getting this wrong would mean a lot of headache later on, hence I would love to get input from some experienced developers on GAE, how they would suggest to model it.
It seems there is a similar question here however the OP didn't provide any code to begin with.
Here are two models:
class Event(ndb.Model):
user = ndb.KeyProperty(kind=User, required=True)
time_of_day = ndb.DateTimeProperty(required=True)
notes = ndb.TextProperty()
timestamp = ndb.FloatProperty(required=True)
class User(UserMixin, ndb.Model):
firstname = ndb.StringProperty()
lastname = ndb.StringProperty()
We need to know who has liked an event, in case that the user may want to unlike it again. Hence we need to keep a reference. But how?
One way would be introducing a RepeatedProperty to the Event class.
class Event(ndb.Model):
....
ndb.KeyProperty(kind=User, repeated=True)
That way any user that would like this Event, would be stored in here. The number of users in this list would determine the number of likes for this event.
Theoretically that should work. However this post from the creator of Python worries me:
Do not use repeated properties if you have more than 100-1000 values.
(1000 is probably already pushing it.) They weren't designed for such
use.
And back to square one. How am I supposed to design this?
RepeatProperty has limitation in number of values (< 1000).
One recommended way to break the limit is using shard:
class Event(ndb.Model):
# use a integer to store the total likes.
likes = ndb.IntegerProperty()
class EventLikeShard(ndb.Model):
# each shard only store 500 users.
event = ndb.KeyProperty(kind=Event)
users = ndb.KeyProperty(kind=User, repeated=True)
If the limitation is more than 1000 but less than 100k.
A simpler way:
class Event(ndb.Model):
likers = ndb.PickleProperty(compressed=True)
Use another model "Like" where you keep the reference to user and event.
Old way of representing many to many in a relational manner. This way you keep all entities separated and can easily add/remove/count.
I would recommend the usual many-to-many relationship using an EventUser model given that the design seems to require unlimited number of user linking an event. The only tricky part is that you must ensure that event/user combination is unique, which can be done using _pre_put_hook. Keeping a likes counter as proposed by #lucemia is indeed a good idea.
You would then would capture the liked action using a boolean, or, you can make it a bit more flexible by including an actions string array. This way, you could also capture action such as signed-up or attended.
Here is a sample code:
class EventUser(ndb.Model):
event = ndb.KeyProperty(kind=Event, required=True)
user = ndb.KeyProperty(kind=User, required=True)
actions = ndb.StringProperty(repeated=True)
# make sure event/user is unique
def _pre_put_hook(self):
cur_key = self.key
for entry in self.query(EventUser.user == self.user, EventUser.event == self.event):
# If cur_key exists, means that user is performing update
if cur_key.id():
if cur_key == entry.key:
continue
else:
raise ValueError("User '%s' is a duplicated entry." % (self.user))
# If adding
raise ValueError("User Add '%s' is a duplicated entry." % (self.user))

Django: Filtering by User Profile or a Foreign Key

For a simple activation key for an account, I have to create the key from random numbers while making sure that I'm not using the same key as another account. Right now, this is what I have:
def get_random_word():
word = ''
i = 0
while i in range(10) and User.objects.filter(activation_key = word).exists():
word += random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
i+=1
return word
The problem, I realize now, is that I use django's built in User class with a user profile like this:
def create_user_info(sender, instance, created, **kwargs):
if created:
UserInfo.objects.create(user=instance)
post_save.connect(create_user_info, sender=User)
class UserInfo(models.Model):
user = models.OneToOneField(User)
pen_name = models.CharField(max_length=30)
activated = models.BooleanField()
activation_key = models.CharField(max_length=40)
def __unicode__(self):
return self.email + '-' + self.pen_name
I need to filter by the user profile. So, in short, how do I filter by a key or, more specifically, a user profile.
first of all, add a related_name to your UserInfo:
class UserInfo(models.Model):
user = models.OneToOneField(User, related_name='profile')
...
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.related_name
and then you can do something like this:
User.objects.filter(profile__activation_key=word).exists()
hope it helps :)
I am not 100% sure what exactly you want to achieve, but here are two answers which might be fitting your needs:
Generally
First you need to connect the Django built-in user model with your UserProfile model. This is done in settings.py setting the AUTH_PROFILE_MODULE setting
AUTH_PROFILE_MODULE = 'myapp.UserProfile' # replace `myapp` with your application name
Answer 1
You can get the user profile for a specific user via user.get_profile() assuming user is a already existing instance of django.contrib.auth.User.
Answer 2
You can query your UserProfile model first by whatever key you want to query for and then map the result set back to your user directly, e.g.:
from myapp.models import UserProfile
profiles = UserProfile.objects.filter(activation_key='abcdef')
for profile in profiles:
print profile.user # this is the profiles connected user instance
First, you don't really care if 2 users have the same activation key. This case is so rare that it can't be a target for any kind of attack.
Second, why not include the username in the activation URL?
This way you are assured to have an unique activation URL for each user.
ex: yourwebsite.com/users/activate/{username}/{activationcode}.
Third, you can use an UUID instead of random. UUID are granted to be unique (well not exactly but 99.99% of the time).
http://docs.python.org/library/uuid.html
Fourth, do you really need to keep the activation code in the profile informations? Why not create an independent activation table, you can even set the primary key to be the activation code. Most of the time, it is better to separate informations that won't be used much from the ones you will use often.
Fifth, You don't really need to query the User table at all. You just need to query the UserProfile table and check if the activation key exists. Then and only then, you can use the OneToOne relation to map to the original user.
You don't even need to add the related_name value to the user relationship. The following query works for your profile model.
User.objects.filter(userinfo__activation_key=word).exists()

Categories