Difference between pinax.apps.accounts, idios profiles, and django.auth.User - python

What's the difference between pinax.apps.accounts and the idios profiles app that were installed with the profiles base project?
As I understand it, the contrib.auth should be just for authentication purpose (i.e. username and password), and the existence of User.names and User.email in the auth model is historical and those fields shouldn't be used; but the distinction between accounts and profiles are lost to me. Why is there pinax.apps.account and idios?

The Pinax account is just a wrapper for that holds the user, timezone and language. user is a foreign key relation to the standard django.auth User model.
class Account(models.Model):
user = models.ForeignKey(User, unique=True, verbose_name=_('user'))
timezone = TimeZoneField(_('timezone'))
language = models.CharField(_('language'), max_length=10, choices=settings.LANGUAGES, default=settings.LANGUAGE_CODE)
def __unicode__(self):
return self.user.username
The idios Profile model basically does the same thing but has some custom methods:
class ProfileBase(models.Model):
# ### could be unique=True if subclasses don't inherit a concrete base class
# ### need to look at this more
user = models.ForeignKey(User, verbose_name=_("user"))
class Meta:
verbose_name = _("profile")
verbose_name_plural = _("profiles")
abstract = True
def __unicode__(self):
return self.user.username
def get_absolute_url(self):
if idios.settings.MULTIPLE_PROFILES:
# ### using PK here is kind of ugly. the alternative is to
# generate a unique slug for each profile, which is tricky
kwargs = {
"profile_slug": self.profile_slug,
"pk": self.pk
}
else:
if idios.settings.USE_USERNAME:
kwargs = {"username": self.user.username}
else:
kwargs = {"pk": self.pk}
return reverse("profile_detail", kwargs=kwargs)
#classmethod
def get_form(cls):
return get_profile_form(cls)
def _default_profile_slug(cls):
return cls._meta.module_name
profile_slug = ClassProperty(classmethod(_default_profile_slug))
Neither of them replicates the authentication functionality of django.auth.User if that is what you are asking. It doesn't look like either one has a dependency on the other either. So if you can't see a good use for both of them, just go with the one that makes sense.

Profiles are meant to be used for public data, or data you'd share with other people and is also more descriptive in nature.
Account data are more like settings for you account that drive certain behavior (language or timezone settings) that are private to you and that control how various aspects of the site (or other apps) function.

Related

Partial updating a ManyToMany field, but keeping its get representation

I've been scratching my head about this problem for a couple of hours now. Basically, I have two models: User and Project:
class User(AbstractUser):
username = None
email = models.EmailField("Email Address", unique=True)
avatar = models.ImageField(upload_to="avatars", default="avatars/no_avatar.png")
first_name = models.CharField("First name", max_length=50)
last_name = models.CharField("Last name", max_length=50)
objects = UserManager()
USERNAME_FIELD = "email"
class Project(models.Model):
name = models.CharField("Name", max_length=8, unique=True)
status = models.CharField(
"Status",
max_length=1,
choices=[("O", "Open"), ("C", "Closed")],
default="O",
)
description = models.CharField("Description", max_length=3000, default="")
owner = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name="project_owner"
)
participants = models.ManyToManyField(User, related_name="project_participants", blank=True)
created_at = models.DateTimeField(auto_now_add=True)
I use standard ModelViewSets for both of them, nothing changed. Then there's my Project serializer:
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = "__all__"
status = serializers.CharField(source="get_status_display", required=False)
owner = UserSerializer()
participants = UserSerializer(many=True)
I use UserSerializers here, because having them achieved first of my two goals:
I wanted to get the user data when getting the project from the API -> owner is a serialized User with all the fields, same for participants, but it's a list of users
I want to be able to partially update the Project, for example add a participant
So I searched through the docs and SO and I always found answers that answer one of those questions, but never both of them.
The thing with my second goal is: when I do the partial update (via PATCH, of course), I get the response that: "Invalid data. Expected a dictionary, but got int." when I pass a list of ints (user ids) for the participants. I thought: okay, maybe I have to pass the whole user data to change it. But then I realised: when I remove the UserSerializer from ProjectSerializer - passing just the list of ints in Postman works just fine. And that is a life saver, cuz who wants to create a request with a whole bunch of data, when I can just pass user ids.
But then of course when I remove the UserSerializer, when I call get project, I get participants: [1,2,3,4,...], not participants: [{"id": 1, "name": "John", ...}, ...}]. And I really want this behavior, because I don't want to make additional API calls just to get the users' data by their IDs.
So summing up my question is: Is there a way to leave those serializers in place but still be able to partially update my model without having to pass whole serialized data to the API (dicts instead of IDs)? Frankly, I don't care about the serializers, so maybe the question is this: Can I somehow make it possible to partially update my Products' related fields like owner or participants just by passing the related entities IDs while still maintaining an ability to get my projects with those fields expanded (serialized entities - dicts, instead of just IDs)?
#Edit:
My view:
from rest_framework import viewsets, permissions
from projects.models import Project
from projects.api.serializers import ProjectSerializer
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
permission_classes = [permissions.IsAuthenticated]
lookup_field = "name"
def get_queryset(self):
if self.request.user.is_superuser:
return Project.objects.all()
else:
return Project.objects.filter(owner=self.request.user.id)
def perform_create(self, serializer):
serializer.save(owner=self.request.user, participants=[self.request.user])
Answer:
To anyone reading this, I've solved this problem and I actually created a base class for all my viewsets that I want this behavior to be in:
from rest_framework.response import Response
class ReadWriteViewset:
write_serializer_class = None
read_serializer_class = None
def update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
instance = self.get_object()
write_serializer = self.write_serializer_class(
instance=instance,
data=request.data,
partial=partial,
)
write_serializer.is_valid(raise_exception=True)
self.perform_update(write_serializer)
read_serializer = self.read_serializer_class(instance)
if getattr(instance, "_prefetched_objects_cache", None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(read_serializer.data)
Then you use it kinda like in here
I'm assuming that you are using a ModelViewSet. You could use different serializers for different methods.
class ProjectViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action in ['create', 'update']:
return WriteProjectSerializer # your serializer not using `UserSerializer` that works for updating
return ProjectSerializer # your default serializer with all data
Edit for using different serializers in same method:
# you can override `update` and use a different serializer in the response. The rest of the code is basically the default behavior
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
write_serializer = WriteProjectSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_update(serializer)
read_serializer = ProjectSerializer(instance)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(read_serializer.data)
A good way to see the default code for all these methods is using Classy DRF. You can see all methods that come with using ModelViewSet and use that code with some changes. Here I'm using the default code for update but changing for a new serializer for the response.

Is it possible in Django to have 2 different types of users with theirs own login fields in the same app?

I have 2 types of users on my site, one is the store owner, I want to log him in with the usual custom user email and password, the other is the buyer, I want to login the buyer using just a pin number only. Is it possible to have both types of login users in the same django app. Thanks in advance.
class Store(models.Model):
store_name = models.CharField(max_length=200)
store_status = models.BooleanField()
store_details = models.CharField(max_length=300, blank = True)
store_balance = models.IntegerField(default=0)
user = models.OneToOneField(User, on_delete=models.CASCADE)
college = models.ForeignKey(Institute, on_delete=models.CASCADE )
def __str__(self):
return str(self.store_name)+" "+ str(self.store_status)
class Customer(models.Model):
name = models.CharField(max_length=200)
branch = models.CharField(max_length=200, choices=BRANCHES)
sem = models.CharField(max_length=200, choices=SEMESTERS)
reg_no = models.IntegerField(default=0)
balance = models.IntegerField(default=0)
pin_no = models.IntegerField()
college = models.ForeignKey(Institute, on_delete=models.CASCADE )
To make a custom authentication you need to add an Authentication Backend. Firstly your customer model is not related to your user model try adding a OnetoOne field in that. After that try adding code like this in one of your apps:-
from django.contrib.auth.backends import BaseBackend
class MyBackend(BaseBackend):
def authenticate(self, request, token=None):
try:
customer = Customer.objects.get(pin_no=token)
user = customer.user
except Customer.DoesNotExist:
return None
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Refer the documentation on Customizing authentication in Django for more information.
Now after making an AuthenticationBackend you need to make it so that Django uses it, you do this by adding it to AUTHENTICATION_BACKENDS in your settings.py, since you want the default username and password to remain something like this would work:
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend', 'path.to.MyBackend']
You have several ways for this. One is making an index place where they choose the option "owner" or "buyer" and after ask for login auth. Other approach is to make url specifying those options already. If you want to do it in the same "form" you could add specification under the hood for manage this input data provided also could be done by several ways, or make a checkbox like so it changes form input. Choose the one that suits you. Does this help you?

Reserved fields of Django model

I'd like to have a Django model with a reserved field, so that no one can set it directly but its value it's generated at saving time. This is useful for example to generate user tokens and I want to prevent developers to directly set a value for the token key. At the same time I would like to be able to treat that field as I do with others, so using __ for fields lookup in queries, or be able to retrieve tokens as:
token = Token.objects.get(key='c331054c00494f6a22f0ebde7a32bf9d4619b988')
So in my mind doing something like:
Token.key = 'my-token-key'
should fail, and even instantiation should fail:
token = Token(key='my-token-key')
So far I came up with this solution, but I'm a bit concerned my changes could break some Django workflow since I'm not sure what my changes will affect:
import binascii
import datetime
import os
from django.contrib.auth import get_user_model
from django.db import models
class Token(models.Model):
"""
An access token that is associated with a user.
"""
id = models.AutoField(primary_key=True)
# By default `get_attname` returns the field `name`,
# but in my case the attribute name is different
_key = models.CharField(max_length=40, unique=True, name='key', db_column='key')
_key.get_attname = lambda: '_key'
name = models.CharField(max_length=255)
user = models.ForeignKey(get_user_model(), related_name='tokens')
created = models.DateTimeField(auto_now_add=True)
last_access_time = models.DateTimeField(null=True, blank=True)
expires = models.DateField(
null=True,
blank=True,
help_text="Leave empty for non-expiring tokens. "
"Once the token has expired you can not extend its validity.",
)
#property
def key(self):
return self._key
#key.setter
def key(self, value):
raise ValueError("Can not set key directly. It is automatically generated when saving the model.")
def save(self, *args, **kwargs):
if not self._key:
self._key = self._generate_key()
super(Token, self).save(*args, **kwargs)
#staticmethod
def _generate_key():
return binascii.hexlify(os.urandom(20)).decode()
#property
def expired(self):
return bool(self.expires and self.expires < datetime.date.today())
def __str__(self):
return '{} - {}'.format(self.user, self.name)
class Meta:
verbose_name = "User Token"
verbose_name_plural = "User Tokens"
unique_together = (('name', 'user'),)
As you can see I tried overriding the get_attname method of the key field (needed because the field name and the property are the same and it would lead to errors loading forms). This seems to work just fine, but I would like to know if this could lead to problems running queries.
Maybe there is a simpler way to do this but I couldn't find anything better.
P.S.: I'm using python2 with Django 1.11
Thanks a lot to everyone!

django model instance method not being called

I want to update my model upon login (to check the authorizations of a person from an external system).
The code of my model looks as follow:
import json
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.signals import user_logged_in
from django.db import models
class Person(AbstractUser):
is_dean = models.BooleanField(null=False, blank=False, default=False)
is_institute_manager = models.BooleanField(null=False, blank=False, default=False)
managed_institutes = models.TextField(blank=True, null=True, default=None)
def get_managed_institutes(self):
return json.loads(self.managed_institutes)
def set_managed_institutes(self, value):
self.managed_institutes = json.dumps(value)
# Signals processing
def check_authorizations(sender, user, request, **kwargs):
...
# check if the user is dean
is_dean = False
# logic to check if the user is dean...
user.is_dean = is_dean
# Check if the user manages institutes
is_institute_manager = False
managed_institutes = list()
# Logic to check if the user is managing institutes ...
user.is_institute_manager = is_institute_manager
user.set_managed_institutes = managed_institutes
user.save()
user_logged_in.connect(check_authorizations)
Surprisingly, the boolean flags get set correctly, but the method set_managed_institute never gets called...
I am quite convinced this a trivial mistake from my end, but I can't figure it out.
That is not how you call methods in Python. You need to do so explicitly:
user.set_managed_institutes(managed_institutes)
Or did you mean to define a property?
#property
def managed_institutes(self):
return json.loads(self._managed_institutes)
#managed_institutes.setter
def managed_institutes(self, value):
self._managed_institutes = json.dumps(value)
But also note, you probably want to use a JsonField anyway. If you're using PostgreSQL, there is one defined in Django directly; otherwise there are several third-party libraries that take care of serializing and deserializing your data on load/save.

Django: combine two ForeignKeys into one field

I need to implement the following:
The user shall be presented with a form that will have a drop down choice menu consisting of property names. There are two types of properties: general properties, i.e. properties common for all users and custom properties, i.e. properties that each user has defined prior to that. The models would look something like that:
class GeneralPropertyName(models.Model):
name = models.CharField(max_length=20)
class CustomPropertyName(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
The drop down menu should have all general properties and only those custom properties that pertain to the user.
First question: how to define such a model?
I need to: 1. somehow unify both properties, 2. take only those items from CustomPropertyName that pertain to the user
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(GeneralPropertyName) ??UNIFY??? ForeignKey(CustomPropertyName)
Second, is there anything special that needs to be done with ModelForm?
class SpecDataForm(ModelForm):
class Meta:
model = SpecData
And the 3rd question is what needs to be done in the view? I will need to use inline formsets since I will have a few dynamic forms like that.
def index(request):
user = User.objects.get(username=request.user.username)
specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)
...
specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')
...
Thanks.
EDIT: Adjusted juliocesar's suggestion to include formsets. Somehow I am getting the following error message: Cannot resolve keyword 'property' into field. Choices are: id, name, selection_title, user
def index(request):
user = User.objects.get(username=request.user.username)
user_specdata_form = UserSpecDataForm(user=user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
You can use a GenericForeignKey to handle it, but you still need more to solve your further questions about forms and view.
I have made an example of how you solve your problem (logged user can select from General properties and his Custom properties, non-logged user only can select General properties). I used model inheritance for the properties (In your sample code it seems that a CustomPropertyName is a PropertyName with other fields). I think inheritance is an easier and a more basic concept than ContentTypes and it fits to your needs.
NOTE: I remove some code like imports to simplify the code.
1) models.py file:
class PropertyName(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class CustomPropertyName(PropertyName): # <-- Inheritance!!
user = models.ForeignKey(User)
def __unicode__(self):
return self.name
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = models.ForeignKey(PropertyName)
NOTES: The field SpecData.property points to PropertyName since all properties are saved in the PropertyName's database table.
2) forms.py file:
from django import forms
from django.db.models import Q
from models import SpecData, PropertyName
def UserSpecDataForm(user=None):
UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))
class SpecDataForm(forms.ModelForm):
property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)
class Meta:
model = SpecData
exclude = ('user',)
return SpecDataForm
NOTES: The trick here is to generate the form SpecDataForm dynamically, by filtering properties according the user specified in the parameter.
3) views.py file:
from forms import UserSpecDataForm
def index(request):
if request.POST:
form = UserSpecDataForm(request.user)(request.POST) # instance=user
if form.is_valid():
spec_data = form.save(commit=False)
spec_data.user = request.user
spec_data.save()
else:
form = UserSpecDataForm(request.user)()
return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))
NOTES: Nothing special here, just a call to form.UserSpecDataForm(request.user) that returns the form class and then instantiate. Also setted the logged-in user to the object returned on save since It was excluded in the form to not show in front-end.
Following this basic example you can do the same with formsets if you need it.
UPDATE:
Formset can be used by adding following code to the view:
user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
The complete project sample can be downloaded from http://ge.tt/904Wg7O1/v/0
Hope this helps
1a) have you looked into django's ContentType framework this will allow you to have generic foreign keys and you can put restrictions on what types of models are acceptable to store in.
1b) I think that the validation for accepting what type of foreign key is acceptable shouldn't be in your model but should be part of your form validation before saving.
2) If you do use a model form you're going to have to define your own custom widget for the propery field. This means you're probably going to have to write you're own render function to render the html from the field. You should also define your own validation function on the form to make sure that only the appropriate data is acceptable to save.
3) I don't think you'll have to do anything you aren't already doing in the views
Use GenericForeignKey:
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
property = GenericForeignKey('content_type', 'object_id')
You can use this to combine the two fields(type & id) into a single choice field.
One way is that you have only one model, make user nullable:
class PropertyName(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(max_length=20)
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(PropertyName)
So, if user is not set, it is a general property. If it is set, it is related to this user.
However, please note that if you need unique property names, that NULL != NULL.
Of course, the suggested GenericForeignKey solution is better for some cases.
Also, you can easily make the normal (non-model) form with that you describe and separate form logic from model logic.

Categories

Resources