I am trying to encrypt my username and email field in Djago's Default Auth model using a Proxy Model method but so far with no luck. Here is what I have attempted so far:
class UserManager(models.Manager):
def from_db_value(self, value, expression, connection):
if value is None or value == '':
return value
# decrypt db value and return
return decrypt(value)
def get_prep_value(self, value):
# prepare value to be saved to the database.
return encrypt(str(value))
class CustomUser(User):
objects = UserManager()
class Meta:
proxy = True
def print_value(self, value):
print('test', value)
def save(self, *args, **kwargs):
self.user = self.print_value(self.user)
super().save(*args, **kwargs)
I am trying to overwrite the User model with Proxy model and a custom Model manager. Any tips whether how can I achieve this would be really appreciated.
Related
I have a custom django user model that does not contain a username field, it uses email instead. I am attempting to implement a 3rd party package that makes queries to the user model based on username. Is there a way to override all queries (get, filter, etc.) to this model through a custom manager or otherwise so that username is simply converted to email?
It would turn this:
User.objects.filter(username="grrrrrr#grrrrrr.com")
into
User.objects.filter(email="grrrrrr#grrrrrr.com")
You can create a QuerySet subclass where you "clean" the incoming kwargs for the get and filter methods and change username to email
class UserQuerySet(models.QuerySet):
def _clean_kwargs(self, kwargs):
if 'username' in kwargs:
kwargs['email'] = kwargs['username']
del kwargs['username']
return kwargs
def filter(self, *args, **kwargs):
kwargs = self._clean_kwargs(kwargs)
return super().filter(*args, **kwargs)
def get(self, *args, **kwargs):
kwargs = self._clean_kwargs(kwargs)
return super().get(*args, **kwargs)
class UserManager(BaseUserManager):
def get_queryset(self):
return UserQuerySet(self.model, using=self._db)
class User(AbstractBaseUser):
objects = UserManager()
The custom manager will need to implement create_user and create_superuser as detailed here in the docs
So I have a model like this.
class Member(BaseModel):
objects = models.Manager()
user = models.ForeignKey('api_backend.User', db_index=True, on_delete=models.CASCADE)
cluster = models.ForeignKey('api_backend.Cluster', on_delete=models.CASCADE)
And a generic api view for the same.
lass MemberPutRetrieveUpdateDeleteView(PutAsCreateMixin, MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
queryset = api_models.Member.objects.all()
permission_classes = [permissions.IsAuthenticated, IsMemberOrKickMembers]
lookup_fields = ['user', 'cluster']
def get_serializer_class(self):
if self.request.method in ['PUT']:
return api_serializers.PartialMemberSerializer
return api_serializers.MemberSerializer
def destroy(self, request, *args, **kwargs):
member = self.get_object()
if member.cluster.owner == member.user:
raise exceptions.ValidationError("cannot delete membership with this cluster as you own it.")
return super(MemberPutRetrieveUpdateDeleteView, self).destroy(request, *args, **kwargs)
I am currently using these mixins.
class PutAsCreateMixin(object):
"""
The following mixin class may be used in order to support PUT-as-create
behavior for incoming requests.
"""
def update(self, request, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object_or_none()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
if instance is None:
if not self.lookup_fields:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup_value = self.kwargs[lookup_url_kwarg]
extra_kwargs = {self.lookup_field: lookup_value}
else:
# add kwargs for additional fields
extra_kwargs = {field: self.kwargs[field] for field in self.lookup_fields if self.kwargs[field]}
serializer.save(**extra_kwargs)
return Response(serializer.data, status=201)
serializer.save()
return Response(serializer.data)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def get_object_or_none(self):
try:
return self.get_object()
except Http404:
if self.request.method == 'PUT':
# For PUT-as-create operation, we need to ensure that we have
# relevant permissions, as if this was a POST request. This
# will either raise a PermissionDenied exception, or simply
# return None.
self.check_permissions(clone_request(self.request, 'POST'))
else:
# PATCH requests where the object does not exist should still
# return a 404 response.
raise
class MultipleFieldLookupMixin(object):
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
So, in my serializer, I have multiple lookup fields -user and cluster. Both of these are foreign keys and have their own kwargs in the url.
So my api url is like this.
path('clusters/<int:cluster>/members/<int:user>/', views.MemberPutRetrieveUpdateDeleteView.as_view())
and I would expect a sample url to be like this:
'clusters/3/members/2/'
where 1 is the id of the cluster and 2 is the id of the member.
So basically a put request to this url must create a member which has:
an user foreign key of id 2
a cluster foreign key of id 3
But when trying to create the same with the mixin, I get the following error.
in __set__
self.field.remote_field.model._meta.object_name,
ValueError: Cannot assign "2": "Member.user" must be a "User" instance.
How can I fix this error? Can someone please help me?
thanks a lot!
The answer is actually very simple,
I hope this will save a lot of someone's time.
I had to change the lookup fields to the following
lookup_fields = ['user_id', 'cluster_id']
and the url kwargs to the following
'clusters/<int:cluster_id>/members/<int:user_id>/'
this way, django knows that only the id of the foreign key is present in the url, and not the foreign key object itself. When perform-creating with the lookup fields, everything works just as expected!
I've got some models with user field.
For this purpose I'd like to create a form mixin that would add self.user instance (which is provided to the form in views). Is it possible ?
Here's the example
class UserFormMixin(object):
"""Removes user instance from kwargs and adding it to object"""
def __init__(self, *args, **kwargs):
super(UserFormMixin, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user')
def save(self, **kwargs):
obj = super(UserFormMixin, self).save(commit=False)
obj.user = self.user
if kwargs['commit']:
return obj.save()
else:
return obj
What I'd like to achieve:
class SomeFormWithUserField(UserFormMixin, ModelForm):
class Meta:
model = SomeModelWithUserField
fields = ['fields without user']
def save(self, **kwargs):
data = super(SomeFormWithUserField, sefl).save(commit=False)
#data already with user prepended
#do some other stuff with data
if kwargs['commit']:
return data.save()
else
return data
class SomeOtherFormWithUser(UserFormMixin, ModelForm):
class Meta:
model = SomeOtherModel
fields = ['some fields without user']
# no need to save here.. standard model form with user prepended on save()
The problem is that UserFormMixin doesn't know about model instance? Or am I wrong here?
I am getting some problems.. like 'commit' kwargs key error.. or object is not saved..
You're close, you just have some logic errors. First, in order to override ModelForm methods, your mixin needs to inherit from ModelForm.
class UserFormMixin(forms.ModelForm):
...
Then, any forms that inherit from it just inherit UserFormMixin, not ModelForm.
class SomeOtherFormWithUser(UserFormMixin):
...
Second, your __init__ method override is incorrect. You need to accept any and all args and kwargs that get passed into it.
def __init__(self, *args, **kwargs):
...
Finally, don't override the save method again, in the subclass. I guess it won't technically hurt anything, but what's the point of inheritance if you're going to repeat code, anyways? If user is not nullable, you can always add an if block to check if self.user is not None before adding it to the model. Of course, if user is not nullable, your model won't likely save without self.user anyways.
This one seems to work fine. Thanks Chris!
If this can be coded better please let me know.
class UserFormMixin(forms.ModelForm):
"""Removes user instance from kwargs and adding it to object"""
def __init__(self, *args, **kwargs):
super(UserFormMixin, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user')
def save(self, commit=True):
obj = super(UserFormMixin, self).save(commit=False)
obj.user = self.user
if commit:
return obj.save()
else:
return obj
class SomeFormWithUserField(UserFormMixin):
class Meta:
model = SomeModelWithUserField
fields = ['fields without user']
def save(self, **kwargs):
data = super(SomeFormWithUserField, sefl).save(commit=False)
#data already with user prepended
#do some other stuff with data
# self.send_mail() f.e.
return data.save()
class SomeOtherFormWithUser(UserFormMixin):
class Meta:
model = SomeOtherModel
fields = ['some fields without user']
# this will work too
I want to populate my modelform to include the IP address, however I'm not able to set the ip as I want, it simply returns "This field is required" for IP address.
models.py
class SignUp(models.Model):
....
ip = models.IPAddressField()
views.py
def my_view(request):
form = SignUpForm(request.POST, ip_addr=get_client_ip(request))
forms.py
class SignUpForm(ModelForm):
class Meta:
model = SignUp
def __init__(self, *args, **kwargs):
self.ip_addr = kwargs.pop('ip_addr', None)
super(SignUpForm, self).__init__(*args, **kwargs)
def clean_ip(self):
return self.ip_addr
You haven't actually set an initial value for the ip form field meaning it's empty. Try:
def __init__(self, *args, **kwargs):
super(SignUpForm, self).__init__(*args, **kwargs)
self.fields['ip'].initial = kwargs.pop('ip_addr', None)
The clean_ip method is for validation upon submission of the form and is only called after form.is_valid() is called. It should check that the value of the field is a valid ip
I don't think you should use clean_ip here. You are not cleaning it anyhow. Either use initial in your view or override save method.
I have posts and attachments. The source of my problem is that I would like it to be possible to create attachments before creating the post they are attached to (because the user uses the fancy Ajax upload widget to upload the attachment before filling out the details of the post). The solution I have arrived at is to give each post a UUID. The UUID is generated when the post's modelform is instantiated (i.e., before creating the post). When attachments are uploaded, they are associated with this UUID. Before I pose my question, here is a sketch of the code in order to show more precisely what is going on:
# models.py
import uuid
from django.db import models
class Post(models.Model):
nonce = models.CharField(max_length=36)
class FooPost(Post):
body = models.TextField()
class UploadedFile(models.Model):
url = models.URLField(max_length=1024)
nonce = models.CharField(max_length=36)
#classmethod
def get_unique_nonce(cls):
while True:
nonce = str(uuid.uuid4())
if not cls.objects.filter(nonce=nonce).exists():
return nonce
class Attachment(UploadedFile):
post = models.ForeignKey(Post)
# views.py
class FooPostForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
self.initial.setdefault('uuid', UploadedFile.get_unique_uuid())
def save(self, *args, **kwargs):
obj = super(FooPostForm, self).save(*args, **kwargs)
if kwargs.get('commit', True):
for file in UploadedFile.objects.filter(nonce=obj.nonce)
Attachment(uploadedfile_ptr=file, post=obj).save_base(raw=True)
return obj
class Meta:
model = FooPost
def foo_post(request):
assert request.method == 'POST'
form = FooPostForm(request.POST)
if form.is_valid():
post = form.save()
# ...
This works well in my app, but I would like it to also work in the admin interface. How can I inline the attachments in the admin form for the post, even though they are linked indirectly by shared nonce instead of by a foreign key?
Provide a custom modelform for the inline:
class AttachmentForm(django.forms.ModelForm):
def save(self, *args, **kwargs):
obj = super(AttachmentForm, self).save(*args, **kwargs)
obj.nonce = self.instance.nonce
if kwargs.get('commit', True):
obj.save()
return obj
Exclude the nonce field from the inline:
class AttachmentInline(admin.TabularInline):
exclude = ['nonce']
model = Attachment
form = AttachmentForm
Come up with a new nonce for a new post:
class PostAdminForm(django.forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PostAdminForm, self).__init__(*args, **kwargs)
self.initial.setdefault('nonce', S3File.get_unique_nonce())
Use the inline:
# A particular kind of post
class FooPostAdmin(admin.ModelAdmin):
form = PostAdminForm
inlines = [AttachmentInline]