Trouble migrating reusable django app models to use a custom user model - django

I need to update an existing project to Django 1.5 to take advantage of its newly available custom user model. However, I'm having trouble migrating reusable apps that contain a model with a foreign key to a user. Currently, the foreign key points to auth.User but with a custom user model, it needs to point to myapp.CustomUser. Hence, some kind of migration is needed. I can't simply create a migration file for it because its a reusable app. It wouldn't be future proof because each time the app is updated, I would need to remember to create that migration again (there might even be migration conflicts) so it's not exactly a plausible solution.
Is there a solution to this problem other than to, maybe, fork each project, add a migration file, and then use that instead?
Some code:
models.py in reusable app
from django.conf import settings
from django.db import models
UserModel = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
class ModelA(models.Model):
user = models.ForeignKey(UserModel)
models.py in my project
from django.conf import settings
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
...
settings.py in my project
AUTH_USER_MODEL = 'myapp.CustomUser'

So if the reusable app has a migration that creates a foreign key to a user, the following can be done to support Django 1.5's custom user model.
try:
from django.contrib.auth import get_user_model
except ImportError: # django < 1.5
from django.contrib.auth.models import User
else:
User = get_user_model()
class Migration(SchemaMigration):
def forwards(self, orm):
db.create_table('reusableapp.modela', (
('user', self.gf('django...ForeignKey')(to=orm["%s.%s" % (User._meta.app_label, User._meta.object_name)])
models = {
...
# this should replace "auth.user"
"%s.%s" % (User._meta.app_label, User._meta.module_name): {
'Meta': {'object_name': User.__name__},
}
"reusableapp.modela": {
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['%s.%s']"% (User._meta.app_label, User._meta.object_name)})
}
}
I'm not sure if this is the best solution but it's being used in apps such as django-reversion.
However, this solution still can pose a problem if you originally started with auth.User and then changed to myapp.customuser, simply because south is honors AUTH_USER_MODEL but the migration for the custom user model hasn't been created yet. This can occur during testing. Ticket #1179 of south addresses this issue (http://south.aeracode.org/ticket/1179).

Related

django simple history - how to create history when the object is not created in admin

I'm using django-simple-history==1.9.0 package with django 1.8.
When I create an object outside the admin and then look at the history of the object in the admin page, it shows a message
This object doesn't have a change history. It probably wasn't added
via this admin site.
I tried setting the user for that object:
user = User.objects.get(username='john')
Poll.objects.get(id=536).history.update(history_user=user)
but that did not solve the problem.
Poll.objects.get(id=536).history.all().count()
returns 1 so there is a history generated.
Any ideas how to make it show the history or how to create an additional history?
I also tried update_change_reason but that did not work at all.
Assuming your django-simple is correctly configured, follow the procedures below
In the model.py file of the app you want to change import django-simple-history, the following excerpt for import:
from simple_history.models import HistoricalRecords
In the model.py file, add the historical attribute as follows:
history = HistoricalRecords()
Example:
from django.db import models
from simple_history.models import HistoricalRecords
class Poll(models.Model):
question = models.CharField(max_length=200)
history = HistoricalRecords()
In order for your changes made outside of admin to appear in Django admin, simply add the following code in the admin.py file:
Import:
from simple_history.admin import SimpleHistoryAdmin
Use the register to configure admin history:
admin.site.register(Pool, SimpleHistoryAdmin)
Example:
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import Pool
# Register your models here.
admin.site.register(Tag, SimpleHistoryAdmin)
After this your history will appear in the admin.
Sources:
https://django-simple-history.readthedocs.io/en/latest/admin.html
https://django-simple-history.readthedocs.io/en/latest/user_tracking.html
Regards,
Felipe Dominguesche
Web Developer
Apparently I need to create the log in the LogEntry as in the example below because django-simple-history does not track changes outside the admin page:
from django.contrib.admin.models import LogEntry
from django.contrib.admin.models import LogEntryManager, ADDITION, CHANGE
user_id = User.objects.all()[0].id
content_type_id = ContentType.objects.get(model='color').id
object_id = 4
object_repr = 'Color object'
action_flag = CHANGE
change_message = 'you changed it!'
LogEntry.objects.log_action(user_id, content_type_id, object_id, object_repr, action_flag, change_message=change_message)

Django Custom User Model Best Practice: User = get_user_model()?

I'm trying to create a custom user class and I'd like to know the best practice for implementing referencing the new user. After following the Django docs method of implementing the custom user as follows in models.py:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
And settings.py
AUTH_USER_MODEL = 'myapp.MyUser'
And admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
admin.site.register(User, UserAdmin)
The Django docs say to use settings.AUTH_USER_MODEL in place of user specifically for ForeignKeys and OnetoOneField, but it's not clear about other instances.
My question is specific to how to refer to the custom user class in views.py. Before defining the user class I was using
from django.contrib.auth.models import User
But after defining a custom class this is no longer correct. I've seen boilerplate code use this method in the beginning of views.py:
from django.contrib.auth import get_user_model
User = get_user_model()
Is this the best practice for referencing the custom user? Or should I just be using settings.AUTH_USER_MODEL in place of where I previously had User?
Using settings.AUTH_USER_MODEL will load the user class lazily after all django apps are loaded. Calling get_user_model() on a module level when your app is initially loaded may result in that the user model app is not loaded and also circular imports.
Update: I read two specific questions:
How to correctly access the user model, contrib or custom.
Djangos get_user_model() is quite simply a call to django.apps get_model() using the settings.AUTH_USER_MODEL. If you are writing apps that might be reused in other projects with other user models, use the get_user_model call. Always. Then it doesn't matter what the user model is.
If you have created your own core.User model and is very confident that your code will only be used in this project, from core.models import User works as well.
When to use the string representation from settings instead of fetching the model.
The string representation will in the end usually call the same django.apps get_model() anyway. By giving a string instead of the class itself in Foreignkeys, OneToOneFields etc you simply don't require the model to be looked up during django app imports, where the user model may not yet be available. So using string representation is simply deferred loading of a model. The same goes for all models.
An also during djangos different major versions this behavior have changed, which is another topic. Notice that get_user_model() have been updated in Django 1.11 for import usage.
https://docs.djangoproject.com/en/1.11/topics/auth/customizing/#referencing-the-user-model
you can go with get_user_model instead User
from django.contrib.auth import get_user_model
User = get_user_model()
get_user_model will Returns the User model that is active in this project.
if you modify(adding new field into it) default User table you need to use get_user_model it will return active User table.
BTW User will return native from django.contrib.auth.models

Django: 'no such table' after extending the User model using OneToOneField

(Django 1.10.) I'm trying to follow this advice on extending the user model using OneToOneField. In my app 'polls' (yes, I'm extending the app made in the 'official' tutorial) I want to store two additional pieces of information about each user, namely, a string of characters and a number.
In my models.py I now have the following:
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
stopien = models.CharField(max_length=100)
pensum = models.IntegerField()
and in admin.py the following:
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from polls.models import Employee
class EmployeeInline(admin.StackedInline):
model = Employee
can_delete = False
verbose_name_plural = 'employee'
class UserAdmin(BaseUserAdmin):
inlines = (EmployeeInline, )
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
When adding a user using the admin panel my two new fields display correctly. However, when I click 'save', or if I don't add any user and just click on the name of my sole admin user in the admin panel, I get the following error:
OperationalError at /admin/auth/user/1/change/
no such table: polls_employee
I see some questions and answers related to similar problems, but they seem to be relevant for older version of Django. Could anyone give me a tip as to what I should do? Ideally I'd want my two additional fields display in the admin panel, though I suspect this might be a task for the future.
I have to confess I do not understand this paragraph from the documentation just following the advice I'm using:
These profile models are not special in any way - they are just Django models that happen to have a one-to-one link with a User model. As such, they do not get auto created when a user is created, but a django.db.models.signals.post_save could be used to create or update related models as appropriate.
Do I need to tie this 'post-save' to some element of the admin panel?
I'd be very greatful for any help!
You need run makemigrations to create a migration for your new model, and then migrate to run the migration and create the database table.
./manage.py makemigrations
./manage.py migrate

South not detecting new model file in model folder

I'm working with an existing django project that uses south. Within each app there's a models folder where models are stored in different files. I have added a new file (shown below) but when I attempt to create migration files for the model, South fails to detect the new file and says: "Nothing seems to have changed." My question is what is the correct way to get south to detect this new model? Thanks.
from django.contrib.auth.models import User, Group
from django.db import models
from django.contrib import admin
class AdgroupEmailRecipients(models.model):
users = models.ForeignKey(User)
class Meta:
app_label = 'wifipromo'
class AdgroupEmailRecipientsAdmin(admin.ModelAdmin):
list_display = ('user_first_name', 'user_last_name', 'user_email')
def user_first_name(self, obj):
return obj.users.first_name
user_first_name.short_description = "First Name"
def user_last_name(self, obj):
return obj.users.last_name
user_last_name.short_description = "Last Name"
def user_email(self, obj):
return obj.users.email
user_email.short_description = "Email"
In the __init__.py file of the models folder, you have to import the model for South or even syncdb to detect it. Basically django is just looking for one file with all your models... and if you import it all in init.py that's what the system will see.

Django Proxy Model Permissions Do Not Appear

I extended Django admin site for my app to allow non-staff/superusers access. This is working just fine.
I created a proxy model for an existing model and registered it to my admin site, however, it doesn't appear for non-staff users. From the documentation I read, my understanding is that proxy models get their own permissions. I checked and these don't appear in the list of available permissions.
Here's my code in case it helps:
Normal Model
class Engagement(models.Model):
eng_type = models.CharField(max_length=5)
environment = models.CharField(max_length=8)
is_scoped = models.BooleanField()
class Meta:
ordering = ['eng_type', 'environment']
app_label = 'myapp'
Proxy Model
class NewRequests(Engagement):
class Meta:
proxy = True
app_label = 'myapp'
verbose_name = 'New Request'
verbose_name_plural = 'New Requests'
Model Admin
class NewRequestsAdmin(ModelAdmin):
pass
def queryset(self, request):
return self.model.objects.filter(is_scoped=0)
Custom Admin Registration
myapps_admin_site.register(NewRequests, NewRequestsAdmin)
I've been managing my DB with South. According to this post, you have to tamper with it a bit by following the instructions it points users to. This was a failure. My DB doesn't have a whole lot of info in it, so I uncommented South and ran a regular syncdb to rule out South. Unfortunately, this is still not working and I'm at a loss. Any help is appreciated.
Edit
This was on Django 1.4
Turns out I didn't do anything wrong. I was looking for the permissions under
myapp | New Request | Can add new request
Permissions fall under the parent model.
myapp | engagement | Can add new request
This is fixed in Django 2.2, quoting release notes:
Permissions for proxy models are now created using the content type of the proxy model rather than the content type of the concrete model. A migration will update existing permissions when you run migrate.
and docs:
Proxy models work exactly the same way as concrete models. Permissions are created using the own content type of the proxy model. Proxy models don’t inherit the permissions of the concrete model they subclass.
There is a workaround, you can see it here: https://gist.github.com/magopian/7543724
It can vary based on your django version, but the priciple is the same.
Tested with Django 1.10.1
# -*- coding: utf-8 -*-
"""Add permissions for proxy model.
This is needed because of the bug https://code.djangoproject.com/ticket/11154
in Django (as of 1.6, it's not fixed).
When a permission is created for a proxy model, it actually creates if for it's
base model app_label (eg: for "article" instead of "about", for the About proxy
model).
What we need, however, is that the permission be created for the proxy model
itself, in order to have the proper entries displayed in the admin.
"""
from __future__ import unicode_literals, absolute_import, division
import sys
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.apps import apps
from django.utils.encoding import smart_text
class Command(BaseCommand):
help = "Fix permissions for proxy models."
def handle(self, *args, **options):
for model in apps.get_models():
opts = model._meta
ctype, created = ContentType.objects.get_or_create(
app_label=opts.app_label,
model=opts.object_name.lower(),
defaults={'name': smart_text(opts.verbose_name_raw)})
for codename, name in _get_all_permissions(opts):
p, created = Permission.objects.get_or_create(
codename=codename,
content_type=ctype,
defaults={'name': name})
if created:
sys.stdout.write('Adding permission {}\n'.format(p))
How to use
create a directory /myproject/myapp/management/commands
create the file /myproject/myapp/management/__init__.py
create the file /myproject/myapp/management/commands/__init__.py
save the code above into /myproject/myapp/management/commands/fix_permissions.py
run /manage.py fix_permissions
This is a known bug in Django: https://code.djangoproject.com/ticket/11154 (check comments for some patches)
As of 2021 and Django 3+, the solution for missing permissions for proxy model is simple, just generate migrations with makemigrations:
app#e31a3ffef22c:~/app$ python manage.py makemigrations my_app
Migrations for 'main':
main/migrations/0193_myproxymodel.py
- Create proxy model MyProxyModel
I came here and wasn't really sure, what is the correct cause/solution to this problem.
For Django 1.11
This issue is related due to the wrong content_type_id in auth_permission table.
By default, it adds the content type of the base model instead of proxy model content type.
I realize this question was closed a while ago, but I'm sharing what worked for me in case it might help others.
It turns out that even though permissions for the proxy models I created were listed under the parent apps (as #chirinosky) has mentioned, and even though I granted my non-super user all permissions, it was still denied access to my proxy models through the admin.
What I had to do was workaround a known Django bug (https://code.djangoproject.com/ticket/11154) and connect to the post_syncdb signal to properly create permissions for the proxy models. The code below is modified from https://djangosnippets.org/snippets/2677/ per some of the comments on that thread.
I placed this in myapp/models.py that held my proxy models. Theoretically this can live in any of your INSTALLED_APPS after django.contrib.contenttypes because it needs to be loaded after the update_contenttypes handler is registered for the post_syncdb signal so we can disconnect it.
def create_proxy_permissions(app, created_models, verbosity, **kwargs):
"""
Creates permissions for proxy models which are not created automatically
by 'django.contrib.auth.management.create_permissions'.
See https://code.djangoproject.com/ticket/11154
Source: https://djangosnippets.org/snippets/2677/
Since we can't rely on 'get_for_model' we must fallback to
'get_by_natural_key'. However, this method doesn't automatically create
missing 'ContentType' so we must ensure all the models' 'ContentType's are
created before running this method. We do so by un-registering the
'update_contenttypes' 'post_syncdb' signal and calling it in here just
before doing everything.
"""
update_contenttypes(app, created_models, verbosity, **kwargs)
app_models = models.get_models(app)
# The permissions we're looking for as (content_type, (codename, name))
searched_perms = list()
# The codenames and ctypes that should exist.
ctypes = set()
for model in app_models:
opts = model._meta
if opts.proxy:
# Can't use 'get_for_model' here since it doesn't return
# the correct 'ContentType' for proxy models.
# See https://code.djangoproject.com/ticket/17648
app_label, model = opts.app_label, opts.object_name.lower()
ctype = ContentType.objects.get_by_natural_key(app_label, model)
ctypes.add(ctype)
for perm in _get_all_permissions(opts, ctype):
searched_perms.append((ctype, perm))
# Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_perms = set(Permission.objects.filter(
content_type__in=ctypes,
).values_list(
"content_type", "codename"
))
objs = [
Permission(codename=codename, name=name, content_type=ctype)
for ctype, (codename, name) in searched_perms
if (ctype.pk, codename) not in all_perms
]
Permission.objects.bulk_create(objs)
if verbosity >= 2:
for obj in objs:
sys.stdout.write("Adding permission '%s'" % obj)
models.signals.post_syncdb.connect(create_proxy_permissions)
# See 'create_proxy_permissions' docstring to understand why we un-register
# this signal handler.
models.signals.post_syncdb.disconnect(update_contenttypes)