Django: How to switch relations between two objects - python

I am having issues updating a website I run. Essentially, the users on the website cannot be used anymore, so people have to create new accounts. However, users have ManyToMany relations with other tables in the database, and I want to be able to move the relations from the old users to the new ones.
Is there any good way to do this in Django or directly in the database? I am using PostgreSQL.

Django has a management command framework for this.
https://docs.djangoproject.com/en/3.1/howto/custom-management-commands/
This should live in PROJECT/APPNAME/management/commands/
from django.core.management import BaseCommand
from django.contrib.auth import get_user_model
User = get_user_model()
#The class must be named Command, and subclass BaseCommand
class Command(BaseCommand):
# Show this when the user types help
help = "fix users relations"
# A command must define handle()
def handle(self, *args, **options):
self.stdout.write("fixing users begin")
for user in User.objects.all():
...user fix code...
self.stdout.write("fix users complete")

Related

How and where to call post_migrate where Django's own Permission model is the sender

In my current project I want to use Django Group model objects as access roles.
I'm trying to programmatically assign permissions to the group instances that should have them (for example, 'delete_something' for 'moderator', and Permission.objects.all() for 'admin') during migration, but even though the code runs without errors, not all permissions are assigned. Specifically, no permissions against User model are assigned. The model itself is defined in a separate application (custom User model from my 'users' app).
Theoretically, this code should kick in when migration is finished and all models are registered. It would then create required groups (I'm using default Django's Group model) and assign permissions to them (also using default Permission instances that are created by Django for each model).
After reading a few similar questions, I have a guess that it must be connected to either the order of migrations, the fact that the User model is defined outside of the app that my code is called, or something specific to Permissions migration.
Currently, my code runs from the AppConfig class of one of my project's apps that contains most of the models, and is called by its ready() function.
I wonder if that is the right place to call that code.
I also think that it would make sense for Permission model to be the signal sender, but I don't understand at what point I should be able to call it (when it is registered/ready) and from which part of my project?
If that is connected to User residing in a different app, how do I make the post_migrate run only after both apps are ready? What should it listen for?
I attach my current apps.py code below.
from django.apps import AppConfig
from django.db.models.signals import post_migrate
class ReviewsConfig(AppConfig):
name = 'reviews'
verbose_name = name.capitalize()
def setup_permissions(self, sender, **kwargs) -> None:
"""Get and set permissions for the groups that should have them."""
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Group, Permission
Review = self.get_model('Review')
Comment = self.get_model('Comment')
# get all auto-generated permissions for Review and Comment models
review_permissions = Permission.objects.filter(
content_type=ContentType.objects.get_for_model(Review))
comment_permissions = Permission.objects.filter(
content_type=ContentType.objects.get_for_model(Comment))
# create new groups if DoesNotExist and assign respective permissions
Group.objects.get_or_create(name='user')
moderators, created = Group.objects.get_or_create(name="moderator")
if not created:
moderator_permissions = (
list(filter(
lambda perm: perm.codename in (
'delete_review',
'change_review'),
review_permissions))
+ list(filter(
lambda perm: perm.codename in (
'delete_comment',
'change_comment'),
comment_permissions))
)
moderators.permissions.add(*moderator_permissions)
admins, created = Group.objects.get_or_create(name="admin")
if not created:
admins.permissions.set(Permission.objects.all())
# run the permissions setup only when migration is finished
def ready(self) -> None:
post_migrate.connect(self.setup_permissions, sender=self)
return super().ready()
Try to call save() after the instance is created.

Using Python 3.7 contextvars to pass state between Django views

I'm building a single database/shared schema multi-tenant application using Django 2.2 and Python 3.7.
I'm attempting to use the new contextvars api to share the tenant state (an Organization) between views.
I'm setting the state in a custom middleware like this:
# tenant_middleware.py
from organization.models import Organization
import contextvars
import tenant.models as tenant_model
tenant = contextvars.ContextVar('tenant', default=None)
class TenantMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
user = request.user
if user.is_authenticated:
organization = Organization.objects.get(organizationuser__is_current_organization=True, organizationuser__user=user)
tenant_object = tenant_model.Tenant.objects.get(organization=organization)
tenant.set(tenant_object)
return response
I'm using this state by having my app's models inherit from a TenantAwareModel like this:
# tenant_models.py
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from organization.models import Organization
from tenant_middleware import tenant
User = get_user_model()
class TenantManager(models.Manager):
def get_queryset(self, *args, **kwargs):
tenant_object = tenant.get()
if tenant_object:
return super(TenantManager, self).get_queryset(*args, **kwargs).filter(tenant=tenant_object)
else:
return None
#receiver(pre_save)
def pre_save_callback(sender, instance, **kwargs):
tenant_object = tenant.get()
instance.tenant = tenant_object
class Tenant(models.Model):
organization = models.ForeignKey(Organization, null=False, on_delete=models.CASCADE)
def __str__(self):
return self.organization.name
class TenantAwareModel(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, related_name='%(app_label)s_%(class)s_related', related_query_name='%(app_label)s_%(class)ss')
objects = models.Manager()
tenant_objects = TenantManager()
class Meta:
abstract = True
In my application the business logic can then retrieve querysets using .tenant_objects... on a model class rather than .objects...
The problem I'm having is that it doesn't always work - specifically in these cases:
In my login view after login() is called, the middleware runs and I can see the tenant is set correctly. When I redirect from my login view to my home view, however, the state is (initially) empty again and seems to get set properly after the home view executes. If I reload the home view, everything works fine.
If I logout and then login again as a different user, the state from the previous user is retained, again until a do a reload of the page. This seems related to the previous issue, as it almost seems like the state is lagging (for lack of a better word).
I use Celery to spin off shared_tasks for processing. I have to manually pass the tenant to these, as they don't pick up the context.
Questions:
Am I doing this correctly?
Do I need to manually reload the state somehow in each module?
Frustrated, as I can find almost no examples of doing this and very little discussion of contextvars. I'm trying to avoid passing the tenant around manually everywhere or using thread.locals.
Thanks.
You're only setting the context after the response has been generated. That means it will always lag. You probably want to set it before, then check after if the user has changed.
Note though that I'm not really sure this will ever work exactly how you want. Context vars are by definition local; but in an environment like Django you can never guarantee that consecutive requests from the same user will be served by the same server process, and similarly one process can serve requests from multiple users. Plus, as you've noted, Celery is a yet another separate process again, which won't share the context.

Django: Run scripts from admin by selecting object's name

I am using Django 1.8 and Python 2.7.8 to create a web interface with MySQL databases, I can just use the Admin page to manipulate different tables. Suppose I have a student object with fields: name, id and gender. How to achieve some function like this:
select student's name: just avoid typo in names
click a button like "Run script"
trigger a Python program to run
I only need output from that Python program like: success or failure.
Thanks very much!
What you are after is admin actions. You write a custom function in the admin, and assign the function name to the admin_actions property.
def incomplete_tasks(modeladmin, request, queryset):
queryset.update(completed=False)
incomplete_tasks.short_description = 'Mark as Not Complete'
class TaskAdmin(admin.ModelAdmin):
list_display = ['title', 'completed']
ordering = ['created']
actions = [incomplete_tasks,]
admin.site.register(Task, TaskAdmin)
https://docs.djangoproject.com/en/1.8/ref/contrib/admin/actions/
https://godjango.com/78-custom-django-admin-actions/

in django 1.8, how to set sender for post_migrate and post_syncdb signal receiver when a custom user model is set?

Following is my code in the signals.py file placed in the package where the auth model is defined.
#receiver(post_migrate, sender=settings.AUTH_USER_MODEL)
def define_groups(sender, **kwargs):
# Create groups
Group.objects.get_or_create(name='Promoter')
Group.objects.get_or_create(name='Client')
Group.objects.get_or_create(name='Superuser')
Group.objects.get_or_create(name='Staff')
The documentation (https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#referencing-the-user-model) states that it should be set as
sender=settings.AUTH_USER_MODEL
while this only works for post_save as mentioned in the documentation example.
I've already tried get_user_model() and also directly using the my_custom_user.models.
get_user_model() returns an error, while setting models as sender works just fine, as -
from . import models
#receiver(post_syncdb, sender=models)
def define_groups(sender, **kwargs):
# Create groups
Group.objects.get_or_create(name='Promoter')
Group.objects.get_or_create(name='Client')
Group.objects.get_or_create(name='Superuser')
Group.objects.get_or_create(name='Staff')
But according to documentation this is not the right way to refer a custom user model and is just an ugly workaround.
Would someone please be able to help me with a solution so i can add these Groups with the first migration of user model.
Thank You
EDIT : using get_user_model() returns the following error -
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
The sender for the post_migrate method is never a model (custom or otherwise), it is the AppConfig instance for the app which was installed.
The docs give the following example for connecting your signal handler in the ready method.
from django.apps import AppConfig
from django.db.models.signals import post_migrate
def my_callback(sender, **kwargs):
# Your specific logic here
pass
class MyAppConfig(AppConfig):
...
def ready(self):
post_migrate.connect(my_callback, sender=self)
Similarly, the sender for post_sync_db signal (note the signal is deprecated) is the module containing the models which were installed.

How Can I Allow Object Editing in the Django Admin For Specific Objects ONLY?

I'm currently writing a site which uses django-guardian to assign object-level permissions which are used throughout the site.
Here is the desired functionality:
I'd like for a user to have permissions to edit a single object (or multiple objects). For example, if there is a user named "Joe", and a model named "Partyline", I may give "Joe" specific permissions to "change_partyline" for 3 specific "Partyline" objects.
When Joe logs into the Django admin panel, I'd like him to be able to edit ONLY his 3 specific "Partyline" objects, since those are the only things he has permission to edit.
Here is the current functionality:
I can assign Joe change_partyline permissions to 3 Partyline objects--no problem. And Joe can log into the admin panel just fine. The problem is that since Joe doesn't have "global" permissions to change ALL partylines, the admin panel says that he does not have any permissions, and won't let him edit anything. I'd like to find a way for the admin to recognize that Joe has permissions to edit only 3 specific objects, and let him view and edit only those objects which he has permissions to work on.
I'd LOVE to find a way to make this work. I'm using the admin extensively for my users to manage things right now, and it would really break presentation to have to move certain functionality out of the admin to other areas on the site.
If you have any suggestions, please let me know!
For reference, here is some shell output demonstrating that the user has change_partyline permissions on a Partyline object:
>>> from django.contrib.auth.models import User
>>> u = User.objects.get(id=2)
>>> from apps.partylines.models import Partyline
>>> p = Partyline.objects.get(id=3)
>>> u.has_perm('partylines.change_partyline', p)
True
And here's my partylines.admin module (which shows how the Partyline module is populated in the admin):
from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from apps.partylines.models import Partyline
class PartylineAdmin(GuardedModelAdmin):
list_display = ('did', 'name', 'listed')
ordering = ('did',)
search_fields = ('did', 'name')
admin.site.register(Partyline, PartylineAdmin)
I've asked a similar question to Lukazs (guardian's author) and he told me that this feature is coming on a future release (see user_can_access_owned_objects_only property on this commit and the related issue). If you're not willing to wait, maybe you can just install the source on the master branch.
Have you thought about overriding queryset on your model? In my case was just enough:
# models.py
from django.db import models
from django.contrib.auth import models as auth_models
class MagazineUser(models.Model):
user = models.ForeignKey(auth_models.User, unique=True)
class Magazine(models.Model):
managers = models.ForeignKey(MagazineUser)
# admin.py
class MagazineAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(admin.ModelAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
user_qs = MagazineUser.objects.filter(user=request.user)
return qs.filter(managers__in=user_qs)
That functionality is known as "row-level permissions", and there is a short explanation of some of the admin methods you'll need to override on the Django wiki here.
For your specific use case, I'd guess you want a ManyToMany relationship between Partyline and User, so that the objects allowed for a user can be retrieved via:
objects = request.user.partyline_set.all()
It might simply be enough to override queryset() and return that object list.

Categories