I have an AppEngine app that I'm migrating to run in Django, using app-engine-patch to get all the goodness of Django - particularly the Admin interface.
One of my models looks like (partially) this:
class Request(db.Model):
requestor = db.UserProperty(auto_current_user_add=True)
When I display a form based on this model I don't display the requestor field, and so when I call the Model's put() method the entity I'm saving doesn't have the requestor property set. This triggers the auto_current_user_add magic, and the user who created the request is automatically added.
Under Django, I'm using the provided Users table. I want this to display as a list of the users of my app, so the model becomes:
from ragendja.auth.google_models import User
class Request(db.Model):
requestor = db.ReferenceProperty(User)
However, this breaks the auto_current_user_add magic in the admin interface - if the user of the admin interface doesn't enter a value for the requestor property, Django sets the property to None, when I'd really like for the Request to have their username inserted automatically.
How can I restore the magic?
My solutions relies on three things:
First: it's possible to override the model's put() method.
Second: users.get_current_user() still provides the correct user, and
Third: ragendja.auth.google_models.User.get_djangouser_for_user() takes a google.appengine.api.users.user object and returns the corresponding Django User object - creating it first if it didn't already exist.
Putting this all together, I have:
class Request(db.Model):
requestor = db.ReferenceProperty(User)
def put(self):
if not self.requestor:
self.requestor = User.get_djangouser_for_user(users.get_current_user())
super(Request, self).put()
This works nicely with the admin interface: the admin can assign any existing user (or use the supplied + sign to create a new user) - if they leave it blank, they'll be assigned as the requestor.
Later when I add a view for users to manage their own requests, this value will be on the 'excluded' list, and the same method will add in their username every time they create a new request.
I'm not sure if this is an optimal solution though; I'm new to Django, so maybe there's a better way to achieve this.
Related
When working with a Django Model with multi-table inheritance set-up as in the docs, the admin app cannot add a new "Restaurant" model if a matching "Place" entry exists - the admin app returns "Place with this name already exists".
Django's ModelForm provides methods for form validation, and the Model provides uniqueness validation.
Which is the best place to enable turning the existing Place entry into a Restaurant?
How would you do this?
For Example, a Place(name="hotdogshop", address="bond street") exists, and the user tries to add a Restaraunt( serves_hot_dogs=True, serves_pizza=False, name="hotdogshop", address="bond street" ). The desired end result would be the same as if we had added the "hotdogshop" as a "Restaraunt" to begin with.
An initial hacky workaround is to insert an extra uniqueness check to the model verification, and switch to using django-typed-models so we can recast models.
For example, add the following pseudo-code to your Model.
def _perform_unique_checks(self, unique_checks):
print("Performing custom Recast Unique Check")
try:
# Search for any existing Model you want to replace.
exists = MyModel.objects.get(...)
if exists:
# django-typed-models can be recast without affecting integrity.
exist.recast('myApp.mySubclassedModel')
# Pretend nothing went wrong, so the rest of the save process continues.
return {}
except TwitterHarvestedUser.DoesNotExist:
pass
return super()._perform_unique_checks(unique_checks)
Be careful around how you merge the data from the previous and new model. Djangos save() method will by default end up replacing all the old models fields even if they are unchanged in the new model.
This doesn't work with MyModel.objects.create()
If a user has multiple projects under their account, how do I authenticate the user so that the content is not only specific to their account, but to their default project. Ideally, the project is right at the top portion of the page, inside the base template of the site, right beside the sign-out link. The user should be able to just change the project without signing out and signing back in. The request object should be loaded with the project, so I could query the contacts model like this:
Contact.objects.filter(project = request.project)
Currently, I have to do the same query like this:
user_project = Project.objects.get(user = request.user, project_name = 'summer')
Contact.objects.filter(project = user_project)
A little nudge in the right direction is much appreciated
As far as you'r going to filter the query by request, so I suppose it's gonna be used in a view class. I had the same situation and I fixed it using Mixin view with Class Based View (CBV) and inherit it to all other my Views Class. And inside this Mixin abstract view class, you can add whatever you need and join it to the request.session dictionary-like object
In general how do we know if user is visiting the page for the first time? Technically, do we store the number of visits to each page in the Model(I am using django) or is there some other pattern that I could follow to ease up the operation.
I m just looking for a design for implementing this.
I think you will have to create your own mechanism for this.
I would make a model storing first visits for every url and user called e.g FirstVisit. Then if a user requests a page in a view you can search if there is an entry in FirstVisit for current user and url and find out if it's his first time or not. After that, if he hasn't visited yet, you store the entry to the FirstVisit model, because he is just going to get the content of the page.
I will try and write the code:
#models.py
class FirstVisit(models.Model):
url = models.URLField()
user = models.ForeignKey('auth.User')
#views.py
def my_view(request):
if not FisrtVisit.objects.filter(user=request.user.id, url=request.path).exists():
#he visits for the first time
#your code...
FisrtVisit(user=request.user, url=request.path).save()
You can create a decorator and put in there this functionality. Then add the decorator to any view you want to store this information and from the decorator pass a flag argument to the view determining if user is there for the first time.
Following #davekr suggestion, I would create a model like FirstVisit with some attributes like ip-address, time registered and instante it on the get method (that is tied to the root url) with some validation if the user is valid or authenticated.
PS: Check this (https://docs.djangoproject.com/en/2.0/ref/request-response/) django docs to indentify the request attributes that could be of your interest to construct the FirstVisit.
Is there a recommended way to pass a variable to all my views? Namely in my case, I want to pass a UserProfile object that Foreign Keys a django.contrib.auth.models.User object. I find most if not all my views need to pull the UserProfile object and putting it in Middleware seems like the way to go. It seems like I could do something like the following (I've seen a couple of solutions online that suggest it):
request.session['userprofile'] = userprofile_object
I don't like this because if my UserProfile model ever has a non-serializable field, it would break request.session.
If you have the AuthenticationMiddleware enabled, you will have a user object in all your views. To get the profile all you need to do is call user.get_profile in your view. For example, to output the id of the profile, you would do {{ user.get_profile.id }}.
If you would prefer not to call the get_profile function of the user object each time, you can add arbitrary items to your request. You would create a new middleware which would simply set
request.user_profile = request.user.get_profile()
Then just register that middleware in your settings.py and you should be good to go. I have used this method in the past for getting user geolocation data pinned to the request object.
This proposal depends on the assumption that userprofile objects only matter when users are already logged in so you can get the logged in user via request.user.
It should be possible to get the userprofile by travelling the foreignkey key relation in reverse like this:
if request.user.is_authenticated():
request.user.userprofile_object_set.all() #gets all related userprofile objects
else:
#...
So I've got a UserProfile in Django that has certain fields that are required by the entire project - birthday, residence, etc. - and it also contains a lot of information that doesn't actually have any importance as far as logic goes - hometown, about me, etc. I'm trying to make my project a bit more flexible and applicable to more situations than my own, and I'd like to make it so that administrators of a project instance can add any fields they like to a UserProfile without having to directly modify the model. That is, I'd like an administrator of a new instance to be able to create new attributes of a user on the fly based on their specific needs. Due to the nature of the ORM, is this possible?
Well a simple solution is to create a new model called UserAttribute that has a key and a value, and link it to the UserProfile. Then you can use it as an inline in the django-admin. This would allow you to add as many new attributes to a UserProfile as you like, all through the admin:
models.py
class UserAttribute(models.Model):
key = models.CharField(max_length=100, help_text="i.e. Age, Name etc")
value = models.TextField(max_length=1000)
profile = models.ForeignKey(UserProfile)
admin.py
class UserAttributeInline(admin.StackedInline):
model = UserAttribute
class UserProfile(admin.ModelAdmin):
inlines = [UserAttibuteInline,]
This would allow an administrator to add a long list of attributes. The limitations are that you cant's do any validation on the input(outside of making sure that it's valid text), you are also limited to attributes that can be described in plain english (i.e. you won't be able to perform much login on them) and you won't really be able to compare attributes between UserProfiles (without a lot of Database hits anyway)
You can store additional data in serialized state. This can save you some DB hits and simplify your database structure a bit. May be the best option if you plan to use the data just for display purposes.
Example implementation (not tested)::
import yaml
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField('auth.User', related_name='profile')
_additional_info = models.TextField(default="", blank=True)
#property
def additional_info(self):
return yaml.load(self._additional_info)
#additional_info.setter
def additional_info(self, user_info_dict):
self._additional_info = yaml.dump(user_info_dict)
When you assign to profile.additional_info, say, a dictionary, it gets serialized and stored in _additional_info instead (don't forget to save the instance later). And then, when you access additional_info, you get that python dictionary.
I guess, you can also write a custom field to deal with this.
UPDATE (based on your comment):
So it appears that the actual problem here is how to automatically create and validate forms for user profiles. (It remains regardless on whether you go with serialized options or complex data structure.)
And since you can create dynamic forms without much trouble[1], then the main question is how to validate them.
Thinking about it... Administrator will have to specify validators (or field type) for each custom field anyway, right? So you'll need some kind of a configuration option—say,
CUSTOM_PROFILE_FIELDS = (
{
'name': 'user_ip',
'validators': ['django.core.validators.validate_ipv4_address'],
},
)
And then, when you're initializing the form, you define fields with their validators according to this setting.
[1] See also this post by Jacob Kaplan-Moss on dynamic form generation. It doesn't deal with validation, though.