How to produce a TabularInline that spans models - python

I have a model (Booking) with a OneToOneField (Thread) that subsequently has a ForeignKey relationship (Message). I would like to show a list of messages on the Booking admin, but with the Thread model in between it appears that this is hard/not possible?
Class Booking(Model):
...
thread = models.OneToOneField('user_messages.Thread', verbose_name='thread')
class Thread(Model):
...
class Message(Model):
thread = models.ForeignKey(Thread, related_name="messages")
Is there a way I can set up my BookingAdmin with an inline that can display messages (spanning across the thread relationship)? Something like:
class MessageInline(TabularInline):
model = Message
fk_name = '???'
class BookingAdmin(ModelAdmin):
inlines = [MessageInline, ]
I'm happy to override the way the Inlines work if that's the best way, but I'm not sure where to tackle that. It looks like overriding *get_formset* might do the trick?

This isn't completely tested yet, but appears to work. The solution is to have an inline and formset with hooks to replace the booking with the attached thread...
class BookingMessageFormset(BaseInlineFormSet):
'''Given a Booking instance, divert to its Thread'''
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
kwargs['instance'] = kwargs['instance'].thread
else:
raise Exception() # TODO Not sure if/when this happens
BaseInlineFormSet.__init__(self, *args, **kwargs)
class MessageInline(admin.TabularInline):
model = Message
formset = BookingMessageFormset
def __init__(self, parent_model, admin_site):
'''Override parent_model'''
super(MessageInline, self).__init__(Thread, admin_site)

Related

DRF - Create a Viewset Mixin/Baseclass to inherit a viewset-action

I have a website that is essentially a wiki for a DnD campaign that I am participating in. As such it has articles of Creatures, Characters, Locations and more. I wanted to use Viewsets to access them easily and wanted to use a Viewset action (together with a custom router) to be able to look for individual records not through pk, but through various query-parameters.
I already have something that works for this, now I would like to apply some inheritance to it to not repeat myself. What I'd like to do is something like this:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
#action(detail=True, url_name="detail-params", url_path=detail_with_params_url_pattern_suffix)
def detail_through_params(self, request, **kwargs):
if self.detail_with_params_url_pattern_suffix == "":
raise InvalidViewsetURLException("URL of view 'detail_through_params' of WikiBaseViewset is not defined!")
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
detail_with_params_url_pattern_suffix = "(?P<name__iexact>.+)"
However, I'm struggling over the fact that the decorator absolutely requires the URL parameter in the base class. Otherwise the code just doesn't compile due to a NameError complaining that detail_with_params_url_pattern_suffix is not defined. If you were to set detail_with_params_url_pattern_suffix="" in the base-class in order to not get an Error when your code is compiled, that still wouldn't matter, as the decorator from my experiments so far still grabs the value of that variable from WikiBaseViewset not CharacterViewSet.
How can I rewrite my BaseClass so that this works? Is there even a way?
I did not find a fully satisfying answer to this problem, but in the end acquiesced with this solution, as it was better than copy pasting.
You might not be able to inherit viewset actions, but you sure can inherit individual methods and then just overwrite them in the child and throw a decorator on top. This lead to this structure:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
def detail_through_params(self, request, **kwargs):
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
#action(detail=True, url_name="detail-params", url_path="(?P<name__iexact>.+)")
def detail_through_params(self, request, **kwargs):
return super().detail_through_params(request, **kwargs)

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.

can you use Form Wizard for Model Forms in django?

I have one model and I've created a form out of the model using ModelForm. Now, I want to spread the form across two pages. For example, the first three fields will appear on the first page then the user clicks next and the last three fields appear on the second page. Then he clicks submit and the user submitted data is added to the database.
I took a look at the docs for the Form Wizard and it seems like it would work for model forms as well? Can someone confirm this?
And if it does, can someone explain the process of creating a WizardView class.
This example is given in the docs and I don't understand what the second two parameters are. Is form_list just a list of form objects that you've instantiated based on your form classes? And what is **kwargs?
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/page-to-redirect-to-when-done/')
Thanks in advance for your help!
Say your model has two fields
class AModel( Model ):
fieldA = CharField()
fieldB = CharField()
We want to set each field in a separate step using a FormWizard. So we create two ModelForms, each showing one field:
class Form1( ModelForm ):
class Meta:
model = AModel
fields = ( 'fieldA', )
class Form2( ModelForm ):
class Meta:
model = AModel
fields = ( 'fieldB', )
We call our form wizard AWizard; the url.py entry should look something like
url( r'^$', AWizard.as_view( [ Form1, Form2 ] ) ),
In the implementation of AWizard we need to make sure all the forms write their data to a single instance, which we then save to the database:
class AWizard( SessionWizardView ):
instance = None
def get_form_instance( self, step ):
if self.instance is None:
self.instance = AModel()
return self.instance
def done( self, form_list, **kwargs ):
self.instance.save()
Notice that we override the method get_form_instance. This method returns the model instance the forms bind to.
You might think (I did), that this method creates an instance for the first request (the first step of the wizard), and then keeps using that same instance for all steps.
Actually, it's a little more complicated. For each request a new instance of AWizard is created, which in turn creates a new AModel instance. So, the steps don't share a single instance to start with.
The magic happens when the last form is submitted. At this point all forms are revalidated, each form calls get_form_instance and they end up populating a single AModel instance.
That instance is then saved in done.
Form Wizard is being built into Django 1.4 so is a good way to go about this. It should do what you want, but you may need a couple of tweaks.
Don't worry about the kwargs in done() at the moment - you're not going to need them.
form_list is the list of forms that you want to use for your steps - from urls.py
urlpatterns = patterns('',
(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
)
[ContactForm1, ContactForm2] will be passed to done() as form_list.
What you will need to do is break your ModelForm into separate forms. The easiest way to do this (if you want your model on several forms) is to not use ModelForm but just create your own form. It's pretty easy:
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
Once your forms reflect the portions of your model, just create the views and patterns as described in the docs and set do_something_with_the_form_data(form_list) to a function that completes your model from the form data and then does a save.
You could use ModelForm but - only if you can persuade it to produce different forms for Form Wizard to use for each step - that's going to be the tricky part.
The view proposed by #wuerg did not work for me, I had to do this:
class AWizard( SessionWizardView ):
def dispatch(self, request, *args, **kwargs):
self.instance = AModel()
return super(ApplyWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance( self, step ):
return self.instance
def done( self, form_list, **kwargs ):
self.instance.save()
return HttpResponseRedirect(reverse(thanks))
I had to alter the solution of #wuerg and #madmen to work in my usecase (saving the Model after every step). The big advantage of this approach is that it always uses the same instance of the AModel instead of creating a new instance for every step:
class AWizard(SessionWizardView):
instance = AModel()
def dispatch(self, request, *args, **kwargs):
return super(AWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance(self, step):
return self.instance
def done(self, form_list, **kwargs):
self.save_model()
return render_to_response('done.html')

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

Categories

Resources