Subclass properties don't appear when extending EndpointsModel - python

I would like to create a BaseModel that is an EndpointsModel to contain properties common across all my models (ie. created_at, modified_at). Then I would like to create a User model that extends that BaseModel.
However, I'm running into an issue where when I look at my "user.create" method in the API Explorer, the request body only shows only shows the BaseModel properties of created_at and modified_at, but not the username property.
Here's what I have:
from endpoints_proto_datastore.ndb import EndpointsModel
from google.appengine.ext import ndb
class BaseModel(EndpointsModel):
created_at = ndb.DateTimeProperty(auto_now_add=True)
modified_at = ndb.DateTimeProperty(auto_now=True)
class User(BaseModel):
username = ndb.StringProperty(required=True)
Here's the API built using Google Cloud Endpoints:
import endpoints
from google.appengine.ext import ndb
from models import User
from protorpc import remote
#endpoints.api(name='user', version='v1',
description='API for User Management')
class UserApi(remote.Service):
#User.method(name='user.create', path='user'):
def create_user(self, user):
user.put()
return user
application = endpoints.api_server([UserApi])

If you go to http://localhost:8080/_ah/api/discovery/v1/apis/user/v1/rest you'll see the discovery document generated by your API. Note that (toward the bottom) the create method on the user resource is shown as taking a BaseModel rather than a User.
Now I don't know why this happens precisely—it's definitely related to the magic being done by EndpointsModel—but I have been able to achieve the result you want by switching the inheritance around, and treating BaseModel like a mixin rather than a base class, this way the User model can inherit directly from EndpointsModel:
class BaseModel:
created_at = ndb.DateTimeProperty(auto_now_add=True)
modified_at = ndb.DateTimeProperty(auto_now=True)
class User(BaseModel, EndpointsModel):
username = ndb.StringProperty(required=True)
It makes sense then to rename BaseModel to something that makes more explicit it's a mixin now.
If you check the same discovery document (or API Explorer) you'll notice create takes a User message after this change.

Related

is there a way to set peewee fields via a mixin?

I'm trying to set up a reusable set of data models which I can include in multiple apps, something like this (I'm using users as an example here, but the actual one is a peewee backend for the Authlib library):
# mixins.py
class UserMixin(peewee.Model):
username = peewee.CharField()
password = peewee.CharField()
def set_password(self):
# do stuff
...
Once that mixin's created, I should be able to import it like this, defining only the additional fields (the defaults will already be there from the mixin)
# models.py
db = peewee.SqliteDatabase(config.get('DATABASE_FILE'))
class BaseModel(peewee.model):
class Meta:
database = db
class User(BaseModel, UserMixin):
email = peewee.CharField()
...
I've seen people do this with SQLAlchemy, but when I use this strategy with peewee it doesn't seem to save the fields properly:
if UserMixin inherits from peewee.Model, it says "unable to resolve import hierarchy" (probably since we're importing from peewee.Model multiple times)
if UserMixin is just an object, then peewee doesn't seem to handle its fields properly: they all end up as unbound instances and don't get saved in the database.
My question: is there an "official way" to create reusable model mixins with fields in peewee?
I've seen other projects (such as flask-login) use mixins, but those are generally additional functions like set_password in this example, and not ones that define the fields themselves.
I have a few potential alternate solutions, like
Define the models themselves, rather than mixins, in the shared file, and override their .Meta.database separately for each models.py entry
Define only the other functions in the mixin; let the fields be defined separately each time in models.py
Use the shared code as a file to copy-paste from rather than importing directly.
But there's probably some cleaner way of doing this?
Here's a simple example:
from peewee import *
db = SqliteDatabase(':memory:')
class Base(Model):
class Meta:
database = db
class UserModelMixin(Model):
username = TextField()
class User(UserModelMixin, Base):
pass
print(User._meta.fields)
#{'id': <AutoField: User.id>, 'username': <TextField: User.username>}
I think the problem was the ordering of your mixins.

Django test client does not automatically serialize factories

Here's my code:
# models.py
class MyModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=10)
...
# views.py
def get_all_models(request):
return JsonResponse({"models": list(MyModel.objects.all())})
# urls.py
path('/mypath', views.get_all_models, name='get_all_models'),
This code works just fine if I visit /mypath. However, when I run an automated test using Django's test client, I get this error:
*** TypeError: Object of type MyModel is not JSON serializable
this is my test:
from django.test import TestCase, Client
from blog.tests.factories.user import UserFactory
from blog.tests.factories.post import PostFactory
class MyModelTest(TestCase):
def setUp(self):
self.user = UserFactory.create()
self.post = MyModelFactory.create(user=self.user)
self.client = Client()
def test_get_all_models(self):
response = self.client.get("/mypath")
pass
I suspect it has something to do with my factories:
import factory
from .models import User, MyModel
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Faker('word')
email = factory.Faker('email')
class MyModelFactory(factory.django.DjangoModelFactory):
class Meta:
model = MyModel
user = factory.SubFactory(UserFactory)
name = factory.Faker('name')
How can I make my factories serializable?
NOTE:
This question is not a duplicate. The other questions linked have view handlers that return HttpResponse objects, instead of JsonResponse objects. This distinction is key to my problem, because the error I'm seeing is related to JSON serialization that is supposed to be addressed by the JsonResponse class.
Also, the other questions do not involve factories. Factories are another key component of what I'm trying to do, which is run integration tests against data generated by factories.
The code you shared assumes JSONResponse will serialize an ORM object, but according to Django documentation, it won't:
https://docs.djangoproject.com/en/3.0/ref/request-response/#jsonresponse-objects
https://docs.djangoproject.com/en/3.0/topics/serialization/#djangojsonencoder
It will work if you serialize the Django ORM object before passing it to JSONResponse
Consider doing the following:
from django.core import serializers
data = serializers.serialize("json", MyModel.objects.all())
https://docs.djangoproject.com/en/3.0/topics/serialization/
django-rest-framework is a very popular lib used in scenarios like the one you shared
https://docs.djangoproject.com/en/3.0/topics/serialization/#djangojsonencoder
what about this:
def get_all_models(request):
return JsonResponse({"models": list(MyModel.objects.all().values())},safe=False)
the point is here:
MyModel.objects.all().values()
safe=False

Resolve circular import issue

I am creating an app using Flask with MongoEngine to connect to MongoDB.
My folder structure looks like this:
app/
__init__.py
mod_users/
__init__.py
constants.py
forms.py
models.py
views.py
mod_games/
__init__.py
constants.py
forms.py
models.py
views.py
Let's say my User and Game models are like the following:
mod_users/models.py
class User(db.Document):
email = db.EmailField()
username = db.StringField()
password = db.StringField()
mod_games/models.py
from app.mod_users.models import User
class Game(db.Document):
title = db.StringField()
creator = db.ReferenceField(User, reverse_delete_rule=db.CASCADE)
likes_count = db.IntField()
Now, my problem is that I would like the User to have a list of the game he likes. But I cannot use a reference field because I would have to import Game which would create a circular import.
This won't work:
from app.mod_games.models import Game
class User(db.Document):
email = db.EmailField()
username = db.StringField()
password = db.StringField()
liked_games = db.ListField(
db.ReferenceField(Game, reverse_delete_rule=db.PULL)
)
I thought about storing in every game a list of users who liked it, and then adding a static method in Game that would retrieve
a list of liked Game for a given user, but that doesn't seem to be a clean and efficient way to resolve this.
Although you have an answer - MongoEngine does cater for this as you can pass the string name of the class to a reference field eg:
class User(db.Document):
email = db.EmailField()
username = db.StringField()
password = db.StringField()
liked_games = db.ListField(
db.ReferenceField('Game', reverse_delete_rule=db.PULL)
)
M:N relationships to be modelled via Association class
Game and User have a relationship, where one Game can be liked by any number of Users and one User can like any number of Games.
This is typical M:N relationship, and this is to be modelled by an association class (take it as class modelling rule).
The class should have:
reference to User
reference to Game
any additional properties of this particular relationship, e.g. how many stars the use gave to this game.
The tuple User - Game must be unique.
When defining this type of class, you import from User and from Game module.
User and Game shall not import this association class (otherwise you would enter into circular references problem again)
As it was mentioned before you can pass a string name of a model to a reference field. That is the first thing you should do to avoid circular imports:
class User(db.Document):
company = db.ReferenceField('Company')
But when you have a method that uses some model, let's say, to aggregate some data, this way above doesn't helps - you still may get an circular imports issue. To resolve this try get_document function. See example below.
from mongoengine.base.common import get_document as get_model
class User(db.Document):
# fields definition omitted
def get_games(self):
Game = get_model('Game')
games = Game.objects.filter(user=self.pk)
# some code here

How to extend the django User model?

I'm trying to implement the Users class from django.contrib.auth.models like this:
from django.db import models
from django.contrib.auth.models import User
class Registration(models.Model):
'''Represents a user registration.'''
user = models.ForeignKey(User)
registration_date = models.DateTimeField(auto_now_add=True, help_text='The date of the registration')
def __str__(self):
return '%s - %s' % (self.user, self.registration_date,)
This user have two attributes enabled by default: username, password
Reading at the code I can see that there are more attributes, like name and email.
How can I enable those hidden (if this is correct) attributes?
First, these attributes are not hidden. Assuming you have "django.contrib.auth" and "django.contrib.contenttypes" in your INSTALLED_APPS, then you have access to the User model as it is defined in your link. See here for the documentation on how to use/access it.
However, since you specified extending the User model, I expect you wanted to add some of your own fields to it (even though your example registration_date exists and is accessible via myuser.date_joined).
The older, more stable and more common way of doing this is similar to what you have. The only difference is to use a OneToOneField(User) instead of a ForeignKey(User). This makes the relationship bidirectional and more convenient by forcing one. You do need to make sure and create a Registration object for every User created.
In fact, there is an example of exactly what you want in the docs for the OneToOneField.
from django.db import models
from django.contrib.auth.models import User
class Registration(models.Model):
user = models.OneToOneField(User)
registration_date = models.DateTimeField(auto_now_add=True)
>>> user = User.objects.get(pk=1)
>>> registration = Registration.objects.create(user=user)
>>> user.registration.registration_date
# Should give the current time
>>> user.get_full_name()
# Should also have all those *hidden* attributes linked above
As of Django 1.5, you can use your own custom User model fairly easily. The documentation for this feature is here. If you are just adding some extra fields, then you can subclass the User model and add your own fields.
from django.db import models
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
# We inherit all those nifty things from AbstractUser
registration_date = models.DateTimeField(auto_now_add=True)
Then enable it in your settings.py by adding AUTH_USER_MODEL = 'myapp.MyUser'. We now have to access the user model a little differently
>>> from django.contrib.auth import get_user_model()
>>> Users = get_user_model()
>>> user = Users.objects.get(pk=1)
>>> user.registration_date
# Should give the current time
>>> user.get_full_name()
# Should have those 'hidden' attributes
All this is available under extending and substituting the User model in the documentation.

Django authentication with modelform

I have two models
from django.contrib.auth.models import User
class Vendor(models.MOdel):
user = models.ForeignKey(User,related_name = 'vendor')
......................................................
......................................................
class Customer(models.MOdel):
user = models.ForeignKey(User,related_name = 'customer')
........................................................
.......................................................
what i want to do is enable login for a Vendor and a customer.The login url of vendor is 'vendor/login' and customer is 'customer/login'.While a vendor submit his credentials,i want to check whether the user is a vendor or not and raise a validation error.I couldn't find a way to accomplish this using django.What i basically need is something like a modelform for User which checks the user is a vendor using a queryset.But django auth doesn't have something like this.
The best strategy for me is to write two Authentication Backends, one that enables the vendor authentication and another that does it for the customer.
AUTHENTICATION_BACKENDS is the settings constant giving the authorization backend tuple.
here can you check the documentation about authentication backends.
Declare your implentations with the settings parameter. The order counts django will try all backends untill one is successful.
You could use Django custom user model, add the type field and use it as a factory source to load Vendor/Customer objects. Such solution worked for me very well. Something to start from:
models.py
from django.contrib.auth.models import AbstractUser
TYPES = (
('Vendor', 'Vendor'),
('Customer', 'Customer'),
)
class MyUser(AbstractUser):
type = models.CharField(max_length=10, choices=TYPES, default='Customer')
This approach is very flexible and scales well if you need to add other types in the future. Small example of redirection approach:
class Base():
def __init__(self, auth_user):
self.user = auth_user
def redirect_to(self):
return ""
class Vendor(Base):
def redirect_to(self):
return "/login/vendor"
class Customer(Base):
def redirect_to(self):
return "/login/customer"
Then in the login view you would just dynamically create the user object:
auth_user = form.get_user()
cls = globals()[auth_user.type]
user = cls(auth_user) # This will return either Vendor or Customer object
return HttpResponseRedirect(user.redirect_to())
You can easily create another user types by just adding new class and implementing the needed methods without even touching the rest of the code.

Categories