Create object if doesn't exist when calling it by related_name - django

NOTE: I'm aware of get_or_create and it does not apply here.
I have a Django app with a lot of users. One of my project apps must keep some kind of user preferences relating to this app, so in its' models.py I've created:
[models.py]
class UserPreferences(models.Model):
user = models.ForeignKey(
get_user_model(),
related_name='adminpanel_preferences'
)
advanced_options_enabled = models.BooleanField(default=False)
force_dark_mode = models.BooleanField(default=False)
... (more boolean fields, integer choices etc)
I don't want to use signals for creating a UserPreferences objects every time new user is being created. My idea was to only create an object when such object is somehow requested. This might sound like a perfect get_or_create use case, however I need to call this object from a template using the related_name e.g:
[template.html]
{% if request.user.adminpanel_preferences.hardcore_mode_enabled %}
...
{% endif %}
And while normally the request.user.adminpanel_preferences would return UserPreferences.None, I want it to silently create the object (using default field values) instead.
Is there a possibility to achieve such behavior without interfering the User model? The UserPreferences are not global app preferences, they only refer to this particular app and I'd love to keep these two independent.
I have also tried using a custom objects manager with customized get_queryset method but it doesn't seem to work when using the related_name.

Related

Django global variable based on user groups

In my Django project I have a database that is populated from outside Django, but needs the Django functionality to display data in the database in a user friendly manner. The legacy database structure is as follows:
class SomeModel(models.Model):
#some fields
group_access = models.ForeignKey(AccessGroup,on_delete=models.CASCADE()
class AccessGroup(models.Model):
group_name = models.CharField(max_length=200)
The users have a custom User profile with manytomany relationship with group names assigned to the specific user:
class CustomUser(models.Model):
#some values
projects = models.ManyToManyField(AccessGroup)
Currently I am able to display data from all groups a user has access to, but what I am looking for is a way to create a drop down menu so that users can switch between groups without the need to log out or reenter group on every view.
You could try something like this:
AccessGroup.objects.filter(CustomUser__pk=1)
Or
CustomUser.objects.filter(AccessGroup__group_name='GropName')
https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_many/
you can extend the django user model, somthing like
from django.contrib.auth.models import User
class CustomUser(models.Model):
projects = models.ManyToManyField(AccessGroup)
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
custom_user = models.ForeignKey(CustomUser, unique=False)
class SomeModel(models.Model):
#some fields
group_access = models.ForeignKey(AccessGroup,on_delete=models.CASCADE()
class AccessGroup(models.Model):
group_name = models.CharField(max_length=200)
then something like this to get the data in your view
def index(request):
AccessGroup.objects.filter(user__id=persion(request.user).id)
I'll assume you know how to get the list of groups, and are just looking as to how to get this list into templates. If not, let me know and I'll explain that as well.
If you're trying to get a global variable into templates, there are really 3 main options:
Make a custom template tag that takes the current user as input, and generates this list as output.
Use Middleware to generate the list, and append it to the current context for each request
Use a method on your user class, or a mixin of it (really easy if you use a custom user class), and just call that method as user.method in your templates. Remember to exclude parentheses from the method call (only in templates), and keep in mind that this method shouldn't accept any parameters other than self.
Thank you everybody for getting me on the right track. What I ended up doing is writing a context processor for checking the user permissions:
#context_processors.py
def check_groups(request):
group_check = AccessGroup.objects.values('id','group_name').filter(projects=request.user.id)
return {
'group_check': group_check,
}
Afterwards I created a Bootstrap-select dropdown in my base.html
<select class="selecpicker">
<optgroup>
<option data-hidden="true">Choose group</option>
{% for grpup in group_check %}
<option val="group.id">{{ group.group_name }}</option>
{% endfor %}
</optgroup>
And the it is just a matter of users using it as means to switch access groups in views and passing the value via ajax to any other template views I come across.
Not the 100% what I was looking for, but it works and my users are happy.

Django - call Model save() without using "objects" Manager get_queryset

I'm developing a legacy Django 1.7 system for a client. The programmers before me overrode the Member model (basically the User model) "objects" property with a filter query that removes anything with "is_deleted" set to "True". I've listed the snippets below:
Member class snippet:
class Member(AbstractUser):
objects = MemberManager()
all_objects = models.Manager()
MemberManager class snippet:
class MemberManager(BaseUserManager):
def get_queryset(self):
return super(MemberManager, self).get_queryset().filter(is_deleted=False)
Now when I try to update a user that has the is_deleted flag set to "True" it fails. Below is an example code snippet. Notice how I use "all_objects" which is the default models.Manager() that returns all records.
user = Member.all_objects.get(pk=id) # id of an is_deleted = True record
user.is_deleted = False
user.save()
This code causes this Django query to run which unfortunately has "is_deleted = 0" included in the WHERE clause, which causes it to not find the record. Below is what shows up in the logs:
UPDATE Member [[snip...]] WHERE (Member.is_deleted = 0 AND Member.id = 6)
Is there any way to call "save()" that will not use the MemberManager.objects get_queryset filter?
I think the problem stems from having MemberManager listed first. As the documentation says:
Take note that the first Manager Django encounters (in the order in which they’re defined in the model) has a special status. Django interprets the first Manager defined in a class as the "default" Manager, and several parts of Django will use that Manager exclusively for that model. As a result, it’s a good idea to be careful in your choice of default manager in order to avoid a situation where overriding get_queryset() results in an inability to retrieve objects you’d like to work with.
Reversing the order of objects and all_objects should fix the problem.
I tried Kevin Christopher Henry's answer and unfortunately changing the default manager messed up the authentication code, which needed to inherit from the "BaseUserManager" class. The solution was to create an "undelete" function in the Member model class that uses the "all_objects" property to undelete the user before it's saved.
class Member(AbstractUser):
objects = MemberManager() # default manager
all_objects = models.Manager()
def undelete(self):
if self.is_deleted:
Member.all_objects.filter(id=self.id).update(is_deleted=False)
Then in my code I did this:
user = Member.all_objects.get(pk=id)
user.undelete()

Possible to do conditional ForeignKey.on_delete in Django?

Through the on_delete option, Django provides various alternatives for what to do with objects that have a foreign key to an object that is being deleted.
I'm wondering if there is a way I could do something similar, but conditionally. Here's the scenario. I am utilizing Django 1.5's new custom User model and all my users have a ForeignKey to Site. Like so:
class TenantSiteUser(AbstractUser):
site = models.ForeignKey(Site, null=True)
If a site is deleted, then I'd prefer to delete all the non-superusers linked to that site (i.e., KASKADE-like behavoir), since their existence is now meaningless. But if its a superuser, I'd prefer to just set the user's site to null (i.e., SET_NULL) and let them keep existing, since that's probably me or someone I work with and we tend to not want to unintentionally delete ourselves.
Is there something I can override to manually do a check and implement this type of on_delete behavior?
EDIT: Here's the code that ended up working for me, based on #Kevin's answer and some study of how the existing handlers work:
def NULLIFY_SUPERUSERS_ELSE_CASCADE(collector, field, sub_objs, using):
superusers = []
for user in sub_objs:
if user.is_superuser:
sub_objs = list(sub_objs)
sub_objs.remove(user)
superusers.append(user)
CASCADE(collector, field, sub_objs, using)
if len(superusers):
collector.add_field_update(field, None, superusers)
class TenantSiteUser(AbstractUser):
site = models.ForeignKey(Site, null=True, on_delete=NULLIFY_SUPERUSERS_ELSE_CASCADE)
The options Django provides (CASCADE, PROTECT etc.) are all functions - here's where they're defined for 1.5.
I haven't tested it, but it should be possible to write your own NULL_OR_CASCADE function and pass that in as your field's on_delete argument.

How to access Django Registration data in template when multiple classes of users extending a BaseProfile

Ok so I'm new to Django (and programming in general and I'm building a website that requires multiple types of users. I created a base class that I have in AUTH_PROFILE_MODULE and have the different user types inheriting from the base class. My question is how can I access the data saved from the the classes inheriting from the base class in my templates. I know I can use user.get_profile.field for anything I have within the base class, but it doesn't work for the fields I have outside of the base class. It might be easier to understand if you can see my code...
models.py
class BaseProfile(models.Model):
user = models.ForeignKey(User, unique=True)
#Other common fields
...
class StudentProfile(BaseProfile):
user_type = models.CharField(max_length=20, default="student")
#Other student specific fields
...
class InstructorProfile(BaseProfile):
user_type = models.CharField(max_length=20, default="instructor")
#Other instructor specific fields
...
settings.py
AUTH_PROFILE_MODULE = 'signup.BaseProfile'
I have separate forms for instructors and students and have user_type as a HiddenInput field in each so it cannot be changed by the user so it defaults to what I want it to. I also set my signal to accommodate the different form information that I need. I can see in the admin that my signal will successfully save user_type as "instructor" or "student" respectively depending on which form is used, but I don't know how to retrieve that in my template. I have tried several variations including:
template.html
{% if user.get_profile.user_type == "instructor" %}
{% if user.user_type == "instructor" %}
{% if user.InstructorProfile.user_type == "instructor" %}
Again I'm very new to programming in general, not just Python, so please forgive me if I didn't provide enough information or explain clearly. Thanks in advance for any help. Let me know if I need to provide any other information. I am unable to find the answer through a search.
The first variation would be the right one, but the problem is that your user_type field is on the subclass, not the base class. When you do get_profile, you only get the base class, as Django has no way of knowing simply from the database what sort of subclass you are expecting.
Rather than putting the user_type field on the subclass and setting a default, put it in the base class, and override your save method to set it appropriately.
You could look at django-polymorphic. It provides a nice mechanism for this sort of thing.

Django - Customizeable UserProfile

So I've got a UserProfile in Django that has certain fields that are required by the entire project - birthday, residence, etc. - and it also contains a lot of information that doesn't actually have any importance as far as logic goes - hometown, about me, etc. I'm trying to make my project a bit more flexible and applicable to more situations than my own, and I'd like to make it so that administrators of a project instance can add any fields they like to a UserProfile without having to directly modify the model. That is, I'd like an administrator of a new instance to be able to create new attributes of a user on the fly based on their specific needs. Due to the nature of the ORM, is this possible?
Well a simple solution is to create a new model called UserAttribute that has a key and a value, and link it to the UserProfile. Then you can use it as an inline in the django-admin. This would allow you to add as many new attributes to a UserProfile as you like, all through the admin:
models.py
class UserAttribute(models.Model):
key = models.CharField(max_length=100, help_text="i.e. Age, Name etc")
value = models.TextField(max_length=1000)
profile = models.ForeignKey(UserProfile)
admin.py
class UserAttributeInline(admin.StackedInline):
model = UserAttribute
class UserProfile(admin.ModelAdmin):
inlines = [UserAttibuteInline,]
This would allow an administrator to add a long list of attributes. The limitations are that you cant's do any validation on the input(outside of making sure that it's valid text), you are also limited to attributes that can be described in plain english (i.e. you won't be able to perform much login on them) and you won't really be able to compare attributes between UserProfiles (without a lot of Database hits anyway)
You can store additional data in serialized state. This can save you some DB hits and simplify your database structure a bit. May be the best option if you plan to use the data just for display purposes.
Example implementation (not tested)::
import yaml
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField('auth.User', related_name='profile')
_additional_info = models.TextField(default="", blank=True)
#property
def additional_info(self):
return yaml.load(self._additional_info)
#additional_info.setter
def additional_info(self, user_info_dict):
self._additional_info = yaml.dump(user_info_dict)
When you assign to profile.additional_info, say, a dictionary, it gets serialized and stored in _additional_info instead (don't forget to save the instance later). And then, when you access additional_info, you get that python dictionary.
I guess, you can also write a custom field to deal with this.
UPDATE (based on your comment):
So it appears that the actual problem here is how to automatically create and validate forms for user profiles. (It remains regardless on whether you go with serialized options or complex data structure.)
And since you can create dynamic forms without much trouble[1], then the main question is how to validate them.
Thinking about it... Administrator will have to specify validators (or field type) for each custom field anyway, right? So you'll need some kind of a configuration option—say,
CUSTOM_PROFILE_FIELDS = (
{
'name': 'user_ip',
'validators': ['django.core.validators.validate_ipv4_address'],
},
)
And then, when you're initializing the form, you define fields with their validators according to this setting.
[1] See also this post by Jacob Kaplan-Moss on dynamic form generation. It doesn't deal with validation, though.