Override select method on peewee - python

I want to implement soft delete mechanism on peewee.
I tried overriding select method on my BaseModel like save method.
class BaseModel(Model):
id = BigAutoField()
created = DateTimeField(default=datetime.datetime.now)
modified = DateTimeField()
deleted = BooleanField(default=False)
def select(self, *args, **kwargs):
super(BaseModel, self).select(*args, **kwargs).where(BaseModel.deleted!=True)
def save(self, *args, **kwargs):
self.modified = datetime.datetime.now()
super(BaseModel, self).save(*args, **kwargs)
class Meta:
database = db
When I try to override select method on my base model it gives following error.
TypeError: select() missing 1 required positional argument: 'self'
Is there any other way to implement soft delete mechanism? What am I missing here?

Don't do this!
Don't ever do this!
Instead just add a new classmethod to your model class and use that instead of .select():
class BaseModel(Model):
#classmethod
def public(cls):
return cls.select().where(cls.deleted != True)
Peewee uses select() internally, and plus, if you choose to override .select() you now have no real way of issuing a query that does not include this filter.

Related

Get Current User in django-filter Method

I have a filter where I need to access the request.user. However, django-filter does not pass it. Without using the messy inspect.stack() is there a way to get the current user in the method member_filter below?
class ClubFilter(django_filters.FilterSet):
member = django_filters.MethodFilter(action='member_filter')
class Meta:
model = Club
fields = ['member']
def member_filter(self, queryset, value):
# get current user here so I can filter on it.
return queryset.filter(user=???)
For example this works but feels wrong...
def member_filter(self, queryset, value):
import inspect
request_user = None
for frame_record in inspect.stack():
if frame_record[3] == 'get_response':
request_user = frame_record[0].f_locals['request'].user
print(request_user)
is there maybe a way to add this to some middleware that injects user into all methods? Or is there a better way?
Yes, you can do it, and it's very easy.
First, define __init__ method in your ClubFilter class that will take one extra argument:
class ClubFilter(django_filters.FilterSet):
# ...
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(ClubFilter, self).__init__(*args, **kwargs)
With having your user saved into attribute inside ClubFilter, you can use it in your filter. Just remember to pass current user from your view inside FilterSet.
Try self.request.user.
Why it must work.
you can access the request instance in FilterSet.qs property, and then filter the primary queryset there.
class ClubFilter(django_filters.FilterSet):
member = django_filters.MethodFilter(action='member_filter')
class Meta:
model = Club
fields = ['member']
#property
def qs(self):
primary_queryset=super(ClubFilter, self).qs
return primary_queryset.filter(user=request.user)

Understanding django Form initialisation

I have a class like so:
class EmailForm(forms.Form):
users = forms.MultipleChoiceField(required=False, widget=MultipleHiddenInput())
subject = forms.CharField(max_length=100)
message = forms.Textarea()
def __init__(self, users, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
self.users.choices = users
# self.fields['users'].choices = []
The commented line at the bottom works perfectly if I use it instead of self.users.
Am I right in thinking that users, subject and message are class level so that is why they are popped out of the attribute list?
So self.fields is the per object copy of the attributes in case I want to change them in some way?
Thanks.
The Form class uses the DeclarativeFieldsMetaclass, which enables the declarative syntax for the fields.
The implementation means that the form class and instance does not actually have an attribute self.field_name for each field. That is why trying to use self.users gives an error.
The fields of the form instance can be accessed as self.fields, which is created when you call super in the __init__ method.
The fields of the form class can be accessed as self.base_fields.

formencode Schema add fields dynamically

Let's take, for example, a User Schema where the site admin sets the number of requested phone numbers:
class MySchema(Schema):
name = validators.String(not_empty=True)
phone_1 = validators.PhoneNumber(not_empty=True)
phone_2 = validators.PhoneNumber(not_empty=True)
phone_3 = validators.PhoneNumber(not_empty=True)
...
Somehow I thought I could simply do:
class MySchema(Schema):
name = validators.String(not_empty=True)
def __init__(self, *args, **kwargs):
requested_phone_numbers = Session.query(...).scalar()
for n in xrange(requested_phone_numbers):
key = 'phone_{0}'.format(n)
kwargs[key] = validators.PhoneNumber(not_empty=True)
Schema.__init__(self, *args, **kwargs)
since I read in FormEncode docs:
Validators use instance variables to store their customization
information. You can use either subclassing or normal instantiation to
set these.
and Schema is called in docs as a Compound Validator and is a subclass of FancyValidator so I guessed it's correct.
But this does not work: simply added phone_n are ignored and only name is required.
Update:
Also I tried both overriding __new__ and __classinit__ before asking with no success...
i had the same problem, i found a solution here:
http://markmail.org/message/m5ckyaml36eg2w3m
all the thing is to use the add_field method of schema in youre init method
class MySchema(Schema):
name = validators.String(not_empty=True)
def __init__(self, *args, **kwargs):
requested_phone_numbers = Session.query(...).scalar()
for n in xrange(requested_phone_numbers):
key = 'phone_{0}'.format(n)
self.add_field(key, validators.PhoneNumber(not_empty=True))
i don't think there's a need to call the parent init

django form custom widget for a "ListField" and displaying a list [duplicate]

I'm experimenting with django-nonrel on appengine and trying to use a djangotoolbox.fields.ListField to implement a many-to-many relation. As I read in the documentation a ListField is something that you can use to make a workaround for djamgo-nonrel not supporting many-to-many relations.
This is an excerpt from my model:
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
So if I am getting this right I am creating a list of foreign keys to another class to show a relationship with multiple instances of another class
With this approach everything works fine ... No Exceptions. I can create `MyClass' objects in code and views. But when I try to use the admin interface I get the following error
No form field implemented for <class 'djangotoolbox.fields.ListField'>
So I though I would try something that I haven't done before. Create my own field. Well actually my own form for editing MyClass instances in the admin interface. Here is what I did:
class MyClassForm(ModelForm):
field = fields.MultipleChoiceField(choices=AnotherClass.objects.all(), widget=FilteredSelectMultiple("verbose_name", is_stacked=False))
class Meta:
model = MyClass
then I pass MyClassForm as the form to use to the admin interface
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
admin.site.register(MyClass, MyClassAdmin)
I though that this would work but It doesn't. When I go to the admin interface I get the same error as before. Can anyone tell what I am doing wrong here ... or if you have any other suggestions or success stories of using the ListField, SetField, etc. from djangotoolbox.fields in the admin interface it would be very much appreciated.
OK, here is what I did to get this all working ...
I'll start from the beginning
This is what what my model looked like
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
I wanted to be able to use the admin interface to create/edit instances of this model using a multiple select widget for the list field. Therefore, I created some custom classes as follows
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
These classes allow the listfield to be used in the admin. Then I created a form to use in the admin site
class MyClassForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyClasstForm,self).__init__(*args, **kwargs)
self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
if self.instance.pk:
self.fields['field'].initial = self.instance.field
class Meta:
model = MyClass
After having done this I created a admin model and registered it with the admin site
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
def __init__(self, model, admin_site):
super(MyClassAdmin,self).__init__(model, admin_site)
admin.site.register(MyClass, MyClassAdmin)
This is now working in my code. Keep in mind that this approach might not at all be well suited for google_appengine as I am not very adept at how it works and it might create inefficient queries an such.
As far as I understand, you're trying to have a M2M relationship in django-nonrel, which is not an out-of-the-box functionality. For starters, if you want a quick hack, you can go with this simple class and use a CharField to enter foreign keys manually:
class ListFormField(forms.Field):
""" A form field for being able to display a djangotoolbox.fields.ListField. """
widget = ListWidget
def clean(self, value):
return [v.strip() for v in value.split(',') if len(v.strip()) > 0]
But if you want to have a multiple selection from a list of models normally you'd have to use ModelMultipleChoiceField, which is also not functional in django-nonrel. Here's what I've done to emulate a M2M relationship using a MultipleSelectField:
Let's say you have a M2M relationship between 2 classes, SomeClass and AnotherClass respectively. You want to select the relationship on the form for SomeClass. Also I assume you want to hold the references as a ListField in SomeClass. (Naturally you want to create M2M relationships as they're explained here, to prevent exploding indexes if you're working on App Engine).
So you have your models like:
class SomeClass(models.Model):
another_class_ids = ListField(models.PositiveIntegerField(), null=True, blank=True)
#fields go here
class AnotherClass(models.Model):
#fields go here
And in your form:
class SomeClassForm(forms.ModelForm):
#Empty field, will be populated after form is initialized
#Otherwise selection list is not refreshed after new entities are created.
another_class = forms.MultipleChoiceField(required=False)
def __init__(self, *args, **kwargs):
super(SomeClassForm,self).__init__(*args, **kwargs)
self.fields['another_class'].choices = [(item.pk,item) for item in AnotherClass.objects.all()]
if self.instance.pk: #If class is saved, highlight the instances that are related
self.fields['another_class'].initial = self.instance.another_class_ids
def save(self, *args, **kwargs):
self.instance.another_class_ids = self.cleaned_data['another_class']
return super(SomeClassForm, self).save()
class Meta:
model = SomeClass
Hopefully this should get you going for the start, I implemented this functionality for normal forms, adjust it for admin panel shouldn't be that hard.
This could be unrelated but for the admin interface, be sure you have djangotoolbox listed after django.contrib.admin in the settings.. INSTALLED_APPS
You could avoid a custom form class for such usage by inquiring for the model object
class ModelListField(ListField):
def __init__(self, embedded_model=None, *args, **kwargs):
super(ModelListField, self).__init__(*args, **kwargs)
self._model = embedded_model.embedded_model
def formfield(self, **kwargs):
return FormListField(model=self._model, **kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
widget = ListFieldWidget
def __init__(self, model=None, *args, **kwargs):
self._model = model
super(FormListField, self).__init__(*args, **kwargs)
self.widget.choices = [(unicode(i.pk), i) for i in self._model.objects.all()]
def to_python(self, value):
return [self._model.objects.get(pk=key) for key in value]
def clean(self, value):
return value

Limit a single record in model for django app?

I want use a model to save the system setting for a django app, So I want to limit the model can only have one record, how to do the limit?
Try this:
class MyModel(models.Model):
onefield = models.CharField('The field', max_length=100)
class MyModelAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
# if there's already an entry, do not allow adding
count = MyModel.objects.all().count()
if count == 0:
return True
return False
An easy way is to use the setting's name as the primary key in the settings table. There can't be more than one record with the same primary key, so that will allow both Django and the database to guarantee integrity.
William is right, but I guess this is the best practice
def has_add_permission(self, *args, **kwargs):
return not MyModel.objects.exists()
As reported in the official Django Documentation:
Note: Don’t use this if all you want to do is determine if at least one result exists.
It’s more efficient to use exists().
https://docs.djangoproject.com/en/dev/ref/models/querysets/#when-querysets-are-evaluated
Overwriting has_add_permission works, but in the given examples it breaks the permissions system in Django(staff without necessary permissions can add settings). Here's a one that doesn't break it:
class SettingAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
base_add_permission = super(SettingAdmin, self).has_add_permission(request)
if base_add_permission:
# if there's already an entry, do not allow adding
count = Setting.objects.all().count()
if count == 0:
return True
return False
A model with a single allowed row is nothing more than a perverted form of a "persisted object" -- maybe even a "persisted singleton"? Don't do that, that's not how models work.
Check out https://github.com/danielroseman/django-dbsettings
It looks like Ali Reza's answer but you can update the existed records and return the error message to any form that uses this model. I believe it is reliable and much easy to control.
class MyModel(models.Model):
...
def clean(self):
super().clean()
if not self.id and MyModel.objects.exists():
raise ValidationError('You cannot add more somethings.')
The following is a class I have created which can be used as a singleton class.
from django.db import models
class SingletonModel(models.Model):
class Meta:
abstract = True
def save(self, *args, **kwargs):
self.__class__.objects.exclude(id=self.id).delete()
super(SingletonModel, self).save(*args, **kwargs)
#classmethod
def load(cls):
try:
return cls.objects.get()
except cls.DoesNotExist:
return cls()
From the above SingletonModel we can create multiple models, all of which will be having only one record
class ProjectSettings(SingletonModel):
max_tickets = models.IntegerField(default=15)
min_tickets = models.IntegerField(default=2)
...
We can access the only object of the settings model as follows
ProjectSettings.load().max_tickets
It is also possible to register ProjectSettings to django admin
#admin.register(ProjectSettings)
class ProjectSettingsAdmin(admin.ModelAdmin):
list_display = [field.name for field in ProjectSettings._meta.get_fields()]
def has_delete_permission(self, request, obj=None):
# Nobody is allowed to delete
return False
You can rewrite the save method on your model. Whenever the user wants to register a new record, you can check the existence then, either save the record or, reject the request.
class MyModel(models.Model):
title = models.CharField(...)
body = models.TextField(...)
def save(self, *args, **kwargs):
if MyModel.objects.exists():
raise ValueError("This model has already its record.")
else:
super().save(*args, **kwargs)
You can also use validators. I prefer to use this method.
Hope you find it useful.

Categories

Resources