How to store field names which user can see.
What is the best/correct way to save rights/access to fields in table.
I want store in UserModel which fields allowed to this user, from DataModel
E.G: user is allowed to see name, commision and id, another user is allowed to see name, seller, price and custom_data_field7
I would recommend django permissions system, or create something simpler, that will decide what fields should be shown based on user type. Depending on where would you like it to show (is it in template or REST API), I could come up with some ideas.
Related
I want to create a database model somewhat similar to the example below:
The idea is that User (or any model) can have multiple different features (or whatever) with different values. Feature can be anything, for example integer (salary), date (birthdate), or multiple selection (competences like C, Python, etc.). To keep the database design simple, I've tried an approach where I have only one Feature table which has optional choices via Choice table, instead of having separate database table for each feature type. User selection for feature value is stored to a User_has_Feature table "value" field, which is a CharField which can store multiple different data types, like integer, date, choice, multiple choice etc.
My question is, how can I validate all data so that it validates against the field data type, and also shows everything correctly in admin or UI (having different UI widgets for different types)?
I've tried an approach where I store the field type for each feature to Field table, which can be CharField, IntegerField, DateField, etc. Then in User_has_Feature model clean() I can dynamically validate against the field type, for example:
FIELDS = {
'DateField': DateField,
'IntegerField': IntegerField,
etc.
}
def clean(self):
class_ = FIELDS.get(self.feature.field)
if class_ in [DateField, IntegerField, ...]:
field = class_()
field.clean(self.value)
elif class_ in [ModelChoiceField, ModelMultipleChoiceField]:
etc.
This approach works fine for validation, but it isn't helping for admin widgets and also data is always handled as a string, instead of integer, date, list, etc. So I've started to investigate option to create a custom ValueField model field, which inherits CharField, and would store all the data as a string to the database, but would change the widget and data type (to_python) dynamically for the value. No success so far, everything I try seems to go overly complex.
For me this seems to be quite common need, thus I would expect that some "easy" solution already exist. Or then I need to try different approach with the database design altogether.
You can use Django Content Types Framework docs
The main idea is that you can create dedicated classes (models) to your features, and CTF could help you to link features to the User (in your case).
I'm using this for linking different models to the Logging model and each logging instance contains a direct link to the needed model instance that it logs.
UPDATE:
Ok, in Django terms we need:
Use ManyToMany for relation User<->Features. You can define this field as features in the user model. So it will be simple calling in the future, like: user.features.all(). Managing related features will be also simple.
Your clean method is correct: you are mapping string names of the str types of fields with classes. It's ok.
Use this trick to make the same mapping but on the admin's side forms
class OurModelAdmin(admin.ModelAdmin):
...
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'Multiple':
kwargs['widget'] = forms.CheckboxSelectMultiple
# mapping is here
return super(OurModelAdmin, self).formfield_for_dbfield(db_field,**kwargs)
...
In a Django app of mine, I need to display, for a list of users (called user_ids), their:
username, avatar and score.
The username is retrieved from the User model, whereas avatar and score are present in the UserProfile model (that has a one-to-one field point to the User model, called user).
Currently my approach is to fetch the full objects (see below), even though I just need 3 attributes from the two models.
What's the most efficient way for me to just retrieve just the required fields, nothing else? Now I know i can do:
usernames = User.objects.filter(id__in=user_ids).values_list('username',flat=True)
scores_and_avatars = UserProfile.objects.filter(user_id__in=user_ids).values_list('score','avatar')
However, these give me separate querysets, so I can't iterate over them as a unified object_list and show each user's username, score and avatar in a Django template. So what would be the most efficient, light-weight way to retrieve and put this information together?
Currently, I'm doing the following to retrieve these three fields: queryset = User.objects.select_related('userprofile').filter(id__in=user_ids)
The most efficient way it's use values, your query will look like this:
usernames = User.objects.filter(id__in=user_ids).values('username', 'userprofile__score', 'userprofile__avatar')
This solution will return dictionary, but you can try to use only instead of values, it'll create django model object with specified fields, but if you will try to access not specified field it'll generate additional query.
usernames = User.objects.filter(id__in=user_ids).select_related('userprofile').only('username', 'userprofile__score', 'userprofile__avatar')
In Django, I want to filter a QuerySet using a list of Users who the active user is following.
I've opted to extend the User class rather than replace it with a custom class, although I'm not sure that was the right choice.
Hence what I have is a UserProfile class, which has a ManyToManyField to other UserProfiles, and a OneToOneField with User.
My QuerySet looks like Entry.objects.filter(author__in=request.user.userprofile.following.all()) but author is a ForeignKeyField to User rather than UserProfile, so I'm about to change Entry.author to point to UserProfiles instead.
So my questions are, in decreasing priority:
Is it right to have author be a UserProfile instead? Because then I have something like entry.author.user.username which is not intuitive.
Might it be better to just replace the builtin User class with a custom class which has the data I need?
Is it right for UserProfile's following to be a ManyToManyField to other UserProfile rather than to User?
I don't recommend this at all. As you said it's not intuitive, an
author should be a user.
No, a one to one relationship to user is better than creating a custom user class.
I think it will look better if you connect followings to users. This way your original query will also work.
In UserProfile model:
following = models.ManyToManyField(User, related_name="followed_by")
In this scenario user.followed_by is a list of UserProfiles, but user.userprofile.following is a list of users.
Since your users can follow each other and Entries are from those people, it makes totally sense to make the author UserProfile so that both models are logically in the same level
Say I have a model User, which has a credits field (IntegerField). When a user registers, I will set the credits field to 0, and I will update the credits for certain events.
I don't want the user know there is a field like this in the db table.
What attribute should I set to the field?
To accomplish the defaulting to 0 part, you can simply use the default argument of the model field.
For the part where you don't want your users to know about the field, you have a couple choices.
Solution 1: Field.editable
Defining your field as follows will cause the field to never show up in a model form.
credits = models.IntegerField(default=0, editable=False)
Downsides
You won't be able to edit the field's value in the admin
Form validation will never take this field into account (e.g., def clean_credits(self): won't run)
Solution 2: ModelForm.exclude|fields
Creating a ModelForm for the model is something you're going to be doing. You can define an exclude attribute on the form's Meta class, and add "credits" to the list. See the docs linked above. You can instead define fields on the Meta class, and omit "credits". The latter of the two options is considered a better practice, particularly when pertaining to security, and is known as a whitelist.
Downsides
You have to remember to define exclude or fields on every exposed form
Updating the "secret" field
The proper way to handle specifying a "secret" field's value when the field isn't in the form is:
# Inside your view's post method (or FormView.form_valid, if you're using generic views)
instance = form.save(commit=False) # Does everything except INSERT into the database
instance.credits = <however many credits you feel like giving the user>
instance.save()
If you didn't do that, and instead just saved the form as-is, the value specified by default would be set to the instance's credits field.
You'll want to use an IntegerField with default=0: credits = models.IntegerField(default=0). Just take care not to show this field to the user in any forms or when displaying the user.
E.g., if you had a ModelForm for User, do not include credits in the fields field of Meta
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.