I've writing a Django site that uses two different databases. One is the local, let's call it, "Django", database that stores all of the standard tables from a pretty standard install -- auth, sites, comments, etc. -- plus a few extra tables.
Most of the data, including users, comes from a database on another server, let's call it the "Legacy" database.
I'm looking for suggestions on clean, pythonic ways of connecting the two databases, particularly in regards to users.
I'm using a proxy model, which works great when I can explicitly use it, but I run into problems when I'm accessing the user object as a related object (for example, when using the built-in django comments system).
Here's what the code looks like:
models.py: (points to the Django database)
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User as AuthUser, UserManager as AuthUserManager, AnonymousUser as AuthAnonymousUser
class UserPerson(models.Model):
user = models.OneToOneField(AuthUser, related_name="person")
person_id = models.PositiveIntegerField(verbose_name='Legacy ID')
def __unicode__(self):
return "%s" % self.get_person()
def get_person(self):
if not hasattr(self, '_person'):
from legacy_models import Person
from utils import get_person_model
Person = get_person_model() or Person
self._person = Person.objects.get(pk=self.person_id)
return self._person
person=property(get_person)
class UserManager(AuthUserManager):
def get_for_id(self, id):
return self.get(person__person_id=id)
def get_for_email(self, email):
try:
person = Person.objects.get(email=email)
return self.get_for_id(person.pk)
except Person.DoesNotExist:
return User.DoesNotExist
def create_user(self, username, email, password=None, *args, **kwargs):
user = super(UserManager,self).create_user(username, email, password, *args, **kwargs)
try:
person_id = Person.objects.get(email=email).pk
userperson, created = UserPerson.objects.get_or_create(user=user, person_id=person_id)
except Person.DoesNotExist:
pass
return user
class AnonymousUser(AuthAnonymousUser):
class Meta:
proxy = True
class User(AuthUser):
class Meta:
proxy=True
def get_profile(self):
"""
Returns the Person record from the legacy database
"""
if not hasattr(self, '_profile_cache'):
self._profile_cache = UserPerson.objects.get(user=self).person
return self._profile_cache
objects = UserManager()
legacy_models.py: (points to the "Legacy" database)
class Person(models.Model):
id = models.AutoField(primary_key=True, db_column='PeopleID') # Field name made lowercase.
code = models.CharField(max_length=40, blank=True, db_column="person_code", unique=True)
first_name = models.CharField(max_length=50, db_column='firstName', blank=True) # Field name made lowercase.
last_name = models.CharField(max_length=50, db_column='lastName', blank=True) # Field name made lowercase.
email = models.CharField(max_length=255, blank=True)
def __unicode__(self):
return "%s %s" % (self.first_name, self.last_name)
def get_user(self):
from models import User
if not hasattr(self,'_user'):
self._user = User.objects.get_for_id(self.pk)
return self._user
user = property(get_user)
class Meta:
db_table = u'People'
I've also whipped up my own middleware, so request.user is the proxy User object also.
The real problem is when I'm using something that has user as a related object, particularly in a template where I have even less control.
In the template:
{{ request.user.get_profile }}
{# this works and returns the related Person object for the user #}
{% for comment in comments %} {# retrieved using the built-in comments app %}
{{ comment.user.get_profile }}
{# this throws an error because AUTH_PROFILE_MODULE is not defined by design #}
{% endfor %}
Short of creating a wrapped version of the comments system which uses my proxy User model instead, is there anything else I can do?
Here's how I resolved it. I stopped using the User proxy altogether.
models.py:
from django.db import models
from legacy_models import Person
from django.contrib.auth.models import User
class UserPerson(models.Model):
user = models.OneToOneField(User, related_name="person")
person_id = models.PositiveIntegerField(verbose_name='PeopleID', help_text='ID in the Legacy Login system.')
def __unicode__(self):
return "%s" % self.get_person()
def get_person(self):
if not hasattr(self, '_person'):
self._person = Person.objects.get(pk=self.person_id)
return self._person
person=property(get_person)
class LegacyPersonQuerySet(models.query.QuerySet):
def get(self, *args, **kwargs):
person_id = UserPerson.objects.get(*args, **kwargs).person_id
return LegacyPerson.objects.get(pk=person_id)
class LegacyPersonManager(models.Manager):
def get_query_set(self, *args, **kwargs):
return LegacyPersonQuerySet(*args, **kwargs)
class LegacyPerson(Person):
objects = LegacyPersonManager()
class Meta:
proxy=True
and legacy_models.py:
class Person(models.Model):
id = models.AutoField(primary_key=True, db_column='PeopleID') # Field name made lowercase.
code = models.CharField(max_length=40, blank=True, db_column="person_code", unique=True)
first_name = models.CharField(max_length=50, db_column='firstName', blank=True) # Field name made lowercase.
last_name = models.CharField(max_length=50, db_column='lastName', blank=True) # Field name made lowercase.
email = models.CharField(max_length=255, blank=True)
def __unicode__(self):
return "%s %s" % (self.first_name, self.last_name)
def get_user(self):
from models import User
if not hasattr(self,'_user'):
self._user = User.objects.get_for_id(self.pk)
return self._user
def set_user(self, user=None):
self._user=user
user = property(get_user, set_user)
class Meta:
db_table = u'People'
Finally, in settings.py:
AUTH_PROFILE_MODULE = 'myauth.LegacyPerson'
This is a simpler solution, but at least it works! It does mean that whenever I want the legacy record I have to call user_profile, and it means that there's an additional query for each user record, but this is a fair trade-off because actually it isn't very likely that I will be doing a cross check that often.
Related
I'm using django-guardian and I encountered some issues with the default mixins. And I want to know if there's a better way to do this.
GitHub Link: https://github.com/iaggocapitanio1/django_homepage
Problem:
If I want to limit access at both the model and object levels, using these two mixins (PermissionRequiredMixin, PermissionListMixin) is not a very easy task. Because the permissions_required attribute is overridden. To get around this I had to create a new attribute "object_permission" and do the following:
Model Looks like:
# Create your models here.
from django.db import models
from localflavor.br import models as localModels
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
class Customer(models.Model):
user: User = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Company(models.Model):
user: User = models.OneToOneField(User, on_delete=models.CASCADE)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='comapnies')
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Project(models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='projects')
class Meta:
permissions = (('read_project', 'Read Project'),)
def __str__(self):
return self.name
class House(models.Model):
rooms = models.IntegerField()
postal_code = localModels.BRPostalCodeField()
project = models.ForeignKey(Project, on_delete=models.CASCADE)
Here I needed to create a new attribute ("object_permission") to limit object-level access
in the View:
class ProjectsListView(PermissionRequiredMixin, PermissionListMixin, ListView):
template_name = 'home/projects.html'
model = models.Project
permission_required = ["homepage.view_project"]
object_permission = ["read_project"]
redirect_field_name = 'next'
login_url = 'login/'
get_objects_for_user_extra_kwargs = {}
def get_object_permission(self, request: HttpRequest = None) -> List[str]:
if isinstance(self.object_permission, str):
perms = [self.object_permission]
elif isinstance(self.object_permission, Iterable):
perms = [p for p in self.object_permission]
else:
raise ImproperlyConfigured("'PermissionRequiredMixin' requires "
"'permission_required' attribute to be set to "
"'<app_label>.<permission codename>' but is set to '%s' instead"
% self.permission_required)
return perms
def get_get_objects_for_user_kwargs(self, queryset):
return dict(user=self.request.user,
perms=self.get_object_permission(self.request),
klass=queryset,
**self.get_objects_for_user_extra_kwargs)
#receiver(post_save, sender=models.Project)
def project_post_save(sender, **kwargs):
"""
Create a Profile instance for all newly created User instances. We only
run on user creation to avoid having to check for existence on each call
to User.save.
"""
project: models.Project = kwargs["instance"]
created: bool = kwargs["created"]
if created:
user = models.User.objects.get(pk=project.owner.user.id)
assign_perm("read_project", user, project)
Am I using the right approach to filter data relative to each user? How do I combine both the page access limitation and the relative data of each user in a class model view?
I'm trying to list all Characters from a User, but my code only return the first Character, can someone lend me a hand?
I'm using the User class from django.contrib.auth.models package.
models.py
class Character(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
def __str__(self):
return self.name
views.py
def sheetList(request):
charL = get_list_or_404(Character, pk=request.user.id)
return render(request, 'sm/sheetList.html',{'charList': charL})
You're filtering on the primary key, which by definition is unique. I suspect you meant to filter on the user:
get_list_or_404(Character, user=request.user)
I am trying to create a resources in bulk. While the resources are created I have the matric_no has to be unique. If the value of an existing matric_no is uploaded together with the some new entries, I get an integrity error 500 because the value already exists and it stops the rest of the values from being created. How can I loop through these values and then check if the value exists, and then skip so that the others can be populated? Here is my code:
**models.py**
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
#python_2_unicode_compatible
class Undergraduate(models.Model):
id = models.AutoField(primary_key=True)
surname = models.CharField(max_length=100)
firstname = models.CharField(max_length=100)
other_names = models.CharField(max_length=100, null=True, blank=True)
card = models.CharField(max_length=100, null=True, blank=True)
matric_no = models.CharField(max_length=20, unique=True)
faculty = models.CharField(max_length=250)
department_name = models.CharField(max_length=250)
sex = models.CharField(max_length=8)
graduation_year = models.CharField(max_length=100)
mobile_no = models.CharField(max_length=150, null=True, blank=True)
email_address = models.CharField(max_length=100)
residential_address = models.TextField(null=True, blank=True)
image = models.CharField(max_length=250,
default='media/undergraduate/default.png', null=True, blank=True)
def __str__(self):
return "Request: {}".format(self.matric_no)
***serializers.py***
from .models import Undergraduate
from .models import Undergraduate
class UndergraduateSerializer(serializers.ModelSerializer):
class Meta:
model = Undergraduate
fields ='__all__'
class CreateListMixin:
"""Allows bulk creation of a resource."""
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
print(list)
kwargs['many'] = True
return super().get_serializer(*args, **kwargs)
**api.py**
from .models import Undergraduate
from rest_framework.viewsets import ModelViewSet
from .serializers import CreateListMixin,UndergraduateSerializer
class UndergraduateViewSet(CreateListMixin, ModelViewSet):
queryset = Undergraduate.objects.all()
serializer_class = UndergraduateSerializer
permission_classes = (permissions.IsAuthenticated,)
**urls.py**
from rest_framework.routers import DefaultRouter
from .api import UndergradMassViewSet
router=DefaultRouter()
router.register(r'ug', UndergradMassViewSet)
This is the updated serializer.py
class UndergraduateSerializer(serializers.ModelSerializer):
class Meta:
model = Undergraduate
fields = ('id', 'surname', 'firstname', 'other_names', 'card','matric_no', 'faculty', 'department_name', 'sex', 'graduation_year', 'mobile_no', 'email_address', 'residential_address')
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
This is how i moved it now
Seriliazers.py
class UndergraduateSerializer(serializers.ModelSerializer):
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
class Meta:
model = Undergraduate
fields = ('id', 'surname', 'firstname', 'other_names', 'card','matric_no', 'faculty', 'department_name', 'sex', 'graduation_year', 'mobile_no', 'email_address', 'residential_address')
read_only_fields = ('id',)
When the list sent has an existing matric_no , it returns 500 ListSeriaizer is not iterable
You definitely have to implement a custom create() method in your serializer to handle such a case as the serializer's default create method expects one object.
Nonetheless, there is a couple of design decisions to consider here:
You can use bulk_create but it has a lot of caveats which are listed in the docs and it would still raise an integrity error since the inserts are done in one single commit. The only advantage here is speed
You can loop over each object and create them singly. This will solve the integrity issue as you can catch the integrity exception and move on. Here you'll lose the speed in 1
You can also check for integrity issues in the validate method before even moving on to create. In this way, you can immediately return an error response to the client, with information about the offending ro. If everything is OK, then you can use 1 or 2 to create the objects.
Personally, I would choose 2(and optionally 3). Assuming you also decide to chose that, this is how your serializer's create method should look like:
def create(self, validated_data):
created_ids = []
for row in validated_data:
try:
created = super().create(row)
created_ids.append(created.pk)
except IntegrityError:
pass
return Undergraduate.objects.filter(pk__in=[created_ids])
So in this case, only the created objects will be returned as response
I am using Django 1.5s Custom User Model. I want to let a user type their username in - and be logged in. NO PASSWORD (for testing anyway). My User Model doesnt have a password. But when i try to login to admin I get the following error:
OperationalError(1054, "Unknown column 'hrms.password' in 'field list'"
It seems to be trying to execute this query in the authenticate() method.
SELECT `myusers`.`password`, `myusers`.`last_login`, `myusers`.`id`, `myusers`.`user`, `myusers`.`name`, `myusers`.`firstname`, `myusers`.`lastname`, `myusers`.`organisation`, `myusers`.`unit`, `myusers`.`grade`, `myusers`.`email`, `myusers`.`position`, `myusers`.`manager` FROM `myusers` WHERE `myusers`.`user` = 'warrenm' "
I do not have the fields password, last_login - I dont know why its trying to get them.
Below is my code.
My Backend (auth.py)
from epmds.application.models import AuthUser
class MyBackend(object):
def get_user(self, user_id):
# get a user from the user_id
try:
return AuthUser.objects.get(pk=user_id)
except AuthUser.DoesNotExist:
return None
def authenticate(self, username=None, password=None):
# check the username/password and return a user
user = AuthUser.objects.get(user=username)
return user
MY Model
class AuthUser(AbstractBaseUser):
id = models.CharField(primary_key=True, max_length=15)
user = models.CharField('username', max_length=20, unique=True)
name = models.CharField(max_length=100)
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
organisation = models.CharField(max_length=100)
email = models.CharField(max_length=50, blank=True, null=True)
USERNAME_FIELD = 'user'
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
return self.first_name
class Meta:
ordering = ('lastname', 'firstname')
managed = False
db_table = 'myusers'
password is part of AbstractBaseUser so it should be added in your AuthUser model as well in table.
As you have managed=False for this model, you need to add that explicitly.
This post extends the error while submitting data in the form django
model.py
from django.db import models
# Create your models here.
class Profile(models.Model):
name = models.CharField(max_length=50, primary_key=True)
assign = models.CharField(max_length=50)
doj = models.DateField()
class Meta:
db_table= 'profile'
def __unicode__(self):
return u'%s' % (self.name)
class working(models.Model):
w_name =models.ForeignKey(Profile, db_column='w_name')
monday = models.IntegerField(null=True, db_column='monday', blank=True)
tuesday = models.IntegerField(null=True, db_column='tuesday', blank=True)
wednesday = models.IntegerField(null=True, db_column='wednesday', blank=True)
class Meta:
db_table = 'working'
def __unicode__(self):
return u'%s ' % ( self.w_name)
view.py
# Create your views here.
from forms import *
from django import http
from django.shortcuts import render_to_response, get_object_or_404
def index(request):
obj=working()
obj.w_name='X'
obj.Monday=1
obj.Tuesday=2
obj.Wednesday =3
obj.save()
return http.HttpResponse('Added')
Here i want to insert the data directly into table ,if person click on http://127.0.0.1:8000/
But it throws below error any thoughts ??
Exception Type: ValueError at /
Exception Value: Cannot assign "u'x'": "working.w_name" must be a "Profile" instance.
I thought you were saying you wanted to inject values into a form?
In this case, it's clear (see it's better than comments), you need to pass in a Profile instance to your working object just as we did in the form clean method in your other post.
def index(request):
try:
profile = Profile.objects.get(pk='X')
except Profile.DoesNotExist:
assert False # or whatever you wish
obj=working()
obj.w_name= profile
obj.Monday=1
obj.Tuesday=2
obj.Wednesday =3
obj.save()
return http.HttpResponse('Added')