Django South migration cannot access custom model Meta attribute - django

I've got a Django app I've created which uses both Django Guardian to manage object-level permissions, and Django South to manage migrations. I've created a model mixin which allows models to define object-level permissions to be assigned as they are created, using a custom Meta attribute. So for example a Message model might look like:
class Message(AutoUserPermissionsMixin, models.Model):
sender = models.ForeignKey(User)
recipient = models.ForeignKey(User)
text = models.TextField(blank=True)
class Meta:
permissions = (
('view_message', 'Can view message'),
('respond_to_message', 'Can respond to message'),
)
user_permissions_to_add = {
'recipient' : ('view_message', 'respond_to_message',),
'sender' : ('view_message',)
}
The AutoUserPermissionsMixin defines a custom save() which reads the model's Meta to know which object-level permissions should be assigned to which field from user_permissions_to_add, and does the assignment. I added the custom Meta field by doing this in the top of file where I define AutoUserPermissionsMixin:
from django.db import models
models.options.DEFAULT_NAMES += ('user_permissions_to_add',)
The problem is, I'm trying to do a data migration in South to create a number of new model instances, and it doesn't assign the object-level permissions, because custom save() methods aren't handled in a migration.
Now I can use the same method being used in the custom save() to try and apply the permissions in the model, which is sync_object_permissions(instance, permissions). I want to read in the permissions at whatever state they are in on the Meta at the time of migration, not hard-code them into the migration. However, when trying to call sync_object_permissions(message_instance, permissions=message_instance._meta.user_permissions_to_add), South throws the error:
AttributeError: 'Options' object has no attribute 'user_permissions_to_add'
So for some reason, the Meta isn't being update with my custom user_permissions_to_add attribute at the time of migration. How can I make sure it's there on the Meta in the migration?

South's modelinspector can be modified to slurp up your extra field on Meta so that this field is avilable during migrations.
Here's the code I've used (put this in your models.py file or some other file that it imports):
#make South record the Meta field "my_special_field" in its orm freezes
import south.modelsinspector as mi
mi.meta_details['my_special_field'] = ['my_special_field', {'default': None, 'ignore_missing': True}]
Note that if you do not include 'ignore_missing': True then South will throw an exception when it processes any models that do not include 'my_special_field' in their Meta options
You can inspect the other Meta fields that South already records by looking at the other fields on south.modelsinspector.meta_details

Related

Create dynamic model field in Django?

In my models.py my model store contains name, brand_name fields
Now,I want to create new field called brand_type in store model dynamically from Django admin,how can I do this?
Can we change schema of model using SchemaEditor? How?
you can't create model's fields via admin panel
you must create fields in models.py
admin panel is only a convinient way to manipulate your database, but not modifiyng its tables
It requires migration while the server is running. Create brand_type field under your model and set blank=True
In your models.py
class Store(models.Model):
name = ...
brand_name = ...
brand_type = models.CharField(max_length = 40, blank= True, null =true)
In your console
./manage.py makemigrations
./manage.py migrate
Extra Stuff:
If you are interested in Text Choices
or
If you wanna make it more dynamic based on your use case,
create a model called BrandType and link it in Store using
brand_type = models.ForeignKey(BrandType,on_delete=models.PROTECT)
models.py
class BrandType(models.Model):
name = ...
some_other fields=
#... Store model follows
admin.py
# other stuff
from .models import BrandType
admin.site.register(BrandType)
Take away: It is not advisable to modify your models.py file using admin directly it will cause integrity issues and there are better ways to achieve your desired functionality.

how to generate builtin model permissions for non-managed models?

I have model like this:
class Venue(models.Model):
name = models.CharField(max_length=255)
class Meta:
managed = False
db_table = 'venue'
permissions = [
('change_venue', 'Can change venue'),
]
It is not managed because it already exists in the database (which was created before django project).
I want to use django's builtin model permissions, but they are not created by default. I tried to add them by changing Meta.permissions field but got an error: The permission codenamed 'change_venue' clashes with a builtin permission for model 'events.Venue'
What should I do? Just make migration and create permissions manually?
Fixed by creating permissions in App.ready hook:
from django.apps import AppConfig
from django.contrib.auth.management import create_permissions
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
create_permissions(self)
Don't know if this counts as valid solution tho
Edit 1
Method above didn't work for new database because models "were not ready/create" when app is ready. So I switched to post_migrate signal, and everything was fine.
Edit 2
After some time I have found global problem about why I don't have permissions and content types in the first place: I simply didn't make migrations for un-managed models. With migrations everything is fine.

How I can Add an extra 'status' field in auth_user Table in Django?

I want to add 'status' field in my django auth_user table, Please let me know how i can add this field. I was trying to add by signup form but i am unable to migrate from there, I am getting error.
Is there are any other option where i can add this field in Django default login functionality.
hey you will have to overide the you model.You can add multiple new column
class User(AbstractBaseUser, PermissionsMixin, BaseModelMixin):
status = models.CharField(max_length=12)
and setting file you have to add the
AUTH_USER_MODEL = "accounts.User"
You can inherith a model from AbstractBaseUser class. It provides the core implementation of a user model, including hashed passwords and tokenized password resets. According to the official documentation: https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#specifying-a-custom-user-model
class MyUser(AbstractBaseUser):
status= models.CharField(max_length=40)
...

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)

Extending Django User for Permission

I just want List user to be under permission , so i just made one custom model like below
from django.db import models
from django.contrib.auth.models import Permission,User
class Mycustomuser(User):
class Meta:
permissions = (
('users','users'),
('view_user', 'View user'),
)
In views i simply called
items=Mycustomuser.objects.all()
It is returning user id with 4 only.
I did so because i made following permission using django guardian
task = MyCustomuser.objects.create()
joe = User.objects.get(username__exact='admin')
assign('view_category', joe, task)
Now i want to check that permission whenever MyCustomuser is called.
It is not necessary and also not recommended to extend User model. See Storing additional information about users.
That said, you can create Permissions without setting permissions model Meta attribute, see Programmatically creating permissions.