I am having a weird behaviour when I add a booleanfield to a through memebership which is included as an inline into the main model. Once saved, it randomly shows the field as True/False. No matter what's on the DB.
class Project(models.Model):
# Relations with other entities.
employees = models.ManyToManyField('staff.Person', through='project.PersonProjectMembership',
related_name='projects')
class PersonProjectMembership(models.Model):
project = models.ForeignKey('project.Project', related_name="person_memberships")
person = models.ForeignKey('staff.Person', related_name="project_memberships")
lead = models.BooleanField(default=False)
class ProjectAdmin(TranslationAdmin, ModelAdmin):
inlines = (PersonProjectMembershipInline,)
class PersonProjectMembershipInline(TranslationStackedInline, admin.StackedInline):
model = Project.employees.through
extra = 1
formset = PersonProjectMembershipInlineFormSet
class PersonProjectMembershipInlineFormSet(BaseInlineFormSet):
def clean(self):
# [...]
Any idea?
It's been a while already. The problem was in some validator, where I was overwriting the PersonProjectMembership.lead value.
Related
TL;DR: I need a some kind of formset for formsets.
I have two different models related to one buisness-entity, and I need to make a form to edit both models like a one form. And I need to create a lot of such forms on the one page like Django inline formset does.
Now I have the following thing:
class Parent(models.Model):
name = models.Charfield()
class FirstChild(models.Model):
name = models.Charfield()
e_id = models.IntegerField()
parent = models.ForeignKey(Parent)
class FirstChildForm(django.forms.ModelForm):
class Meta:
model = Child
fields = ('name', 'e_id', 'parent')
widgets = {'parent': forms.TextInput}
And I render a lot of them using inline formsets:
formset_class = inlineformset_factory(Parent, FirstChild,
form=FirstChildForm, extra=1)
But now I have to add second child model and a form for it, and still render it like an one inline form, but make it form actually edit two models. Like this:
class SecondChild(models.Model):
name = models.Charfield()
e_id = models.IntegerField()
parent = models.ForeignKey(Parent)
class SecondChildForm(django.forms.ModelForm):
class Meta:
model = Child
fields = ('name', 'e_id', 'parent')
widgets = {'parent': forms.TextInput}
formset_class = inlineformset_factory(models=[Parent, FirstChild],
forms=[FirstChildForm, SecondChildForm],
extra=1)
As far as I understand, Django formsets cannot work with multiple models right now.
So which way should I choose to implement this behaviour and do not broke all django conceptions?, I cannot use some extra libraries so I have to implement everything by myself and I use django 1.6 if it is important.
So, finally I used this approach as a base: https://micropyramid.com/blog/how-to-use-nested-formsets-in-django/
I trying to test the Django forms and widgets but it is returning false instead of true. The test looks right but not sure.I have added the test form, interest form and player class
class TestForm(TestCase):
def test_player_interests_fail(self):
form = PlayerInterestsForm(data={'interests': 'sport'})
self.assertEqual(form.is_valid(), True)
class PlayerInterestsForm(forms.ModelForm):
class Meta:
model = Player
fields = ('interests', )
widgets = {'interests': forms.CheckboxSelectMultiple}
class Player(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
quizzes = models.ManyToManyField(Quiz, through='TakenQuiz')
tournaments = models.ManyToManyField(Tournament, through='TakenTournament')
interests = models.ManyToManyField(Subject, related_name='interested_player')
def get_unanswered_questions(self, quiz):
answered_questions = self.quiz_answers\
.filter(answer__question__quiz=quiz)\
.values_list('answer__question__pk', flat=True)
questions = quiz.questions.exclude(pk__in=answered_questions).order_by('text')
return questions
def get_unanswered_tournament_questions(self, tournament):
answered_questions = self.tournament_answers\
.filter(answer__question__tournament=tournament)\
.values_list('answer__question__pk', flat=True)
questions = tournament.questions.exclude(pk__in=answered_questions).order_by('text')
return questions
def __str__(self):
return self.user.username
class Meta:
verbose_name_plural = "Player"
There are two issues with your code:
Player.interests is a ManyToManyField. That means the form expects the data to be a list of primary keys for the selected Subjects - passing a string value will not work. You have to pass integer IDs of Subject objects.
A CheckboxSelectMultiple allows multiple objects to be selected, which means you have to pass a list, not a single value.
So you need to do something like this:
def test_player_interests_fail(self):
# If you haven't already, you need to create a Subject
# I don't know how your Subject model is defined, but something like this
s = Subject.objects.create(name='Sport')
form = PlayerInterestsForm(data={'interests': [s.pk]})
self.assertEqual(form.is_valid(), True)
I wonder how to serialize the mutual relation between objects both ways with "djangorestframework". Currently, the relation only shows one way with this:
class MyPolys(models.Model):
name = models.CharField(max_length=20)
text = models.TextField()
poly = models.PolygonField()
class MyPages2(models.Model):
name = models.CharField(max_length=20)
body = models.TextField()
mypolys = models.ManyToManyField(MyPolys)
# ...
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPages2
# ...
class MyPolyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPolys.objects.all()
serializer_class = srlz.MyPolysSerializer
class MyPages2ViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPages2.objects.all()
serializer_class = srlz.MyPages2Serializer
The many-to-many relation shows up just fine in the api for MyPages2 but nor for MyPolys. How do I make rest_framework aware that the relation goes both ways and needs to be serialized both ways?
The question also applies to one-to-many relations btw.
So far, from reading the documentation and googling, I can't figure out how do that.
Just do it like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields =('id','name','text','poly')
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
mypolys = MyPolysSerializer(many=True,read_only=True)
class Meta:
model = testmodels.MyPages2
fields =('id','name','body','mypolys')
I figured it out! It appears that by adding a mypolys = models.ManyToManyField(MyPolys) to the MyPages2 class, Django has indeed automatically added a similar field called mypages2_set to the MyPolys class, so the serializer looks like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields = ('name', 'text', 'id', 'url', 'mypages2_set')
I found out by inspecting an instance of the class in the shell using ./manage.py shell:
pol = testmodels.MyPolys.objects.get(pk=1)
pol. # hit the tab key after '.'
Hitting the tab key after the '.' reveals additional fields and methods including mypages2_set.
I have 2 models:
1: KW (individual keywords)
2: Project (many keywords can belong to many different projects)
class KW(models.Model):
...
project = models.ManyToManyField('KWproject', blank=True)
class KWproject(models.Model):
ProjectKW = models.CharField('Main Keyword', max_length=1000)
author = models.ForeignKey(User, editable=False)
Now when user is in Admin for KWproject they should be able to see all keywords belonging to selected project in list_display. I achieved this but it doesn't feel like proper way.
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
list_display = ('Keywordd', 'author')
def Keywordd(self, obj):
return '%s' % (obj.id, obj.ProjectKW)
Keywordd.allow_tags = True
Keywordd.admin_order_field = 'ProjectKW'
Keywordd.short_description = 'ProjectKW'
Is there better way to link and then list_display all items that have reverse relationship to the model? (via "project" field in my example)
As per the Django Admin docs:
ManyToManyField fields aren’t supported, because that would entail
executing a separate SQL statement for each row in the table. If you
want to do this nonetheless, give your model a custom method, and add
that method’s name to list_display. (See below for more on custom
methods in list_display.)
So, you may opt to implement a custom model method like so:
# models.py
class KW(models.Model):
...
project = models.ManyToManyField('KWproject', blank=True)
class KWproject(models.Model):
ProjectKW = models.CharField('Main Keyword', max_length=1000)
author = models.ForeignKey(User, editable=False)
def all_keywords(self):
# Retrieve your keywords
# KW_set here is the default related name. You can set that in your model definitions.
keywords = self.KW_set.values_list('desired_fieldname', flat=True)
# Do some transformation here
desired_output = ','.join(keywords)
# Return value (in example, csv of keywords)
return desired_output
And then, add that model method to your list_display tuple in your ModelAdmin.
# admin.py
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
list_display = ('Keywordd', 'author', 'all_keywords')
def Keywordd(self, obj):
return '%s' % (obj.id, obj.ProjectKW)
Keywordd.allow_tags = True
Keywordd.admin_order_field = 'ProjectKW'
Keywordd.short_description = 'ProjectKW'
Do take note: This can potentially be a VERY EXPENSIVE operation. If you are showing 200 rows in the list, then a request to the page will execute 200 additional SQL queries.
Even though a field is marked as 'editable=False' in the model, I would like the admin page to display it. Currently it hides the field altogether.. How can this be achieved ?
Use Readonly Fields. Like so (for django >= 1.2):
class MyModelAdmin(admin.ModelAdmin):
readonly_fields=('first',)
Update
This solution is useful if you want to keep the field editable in Admin but non-editable everywhere else. If you want to keep the field non-editable throughout then #Till Backhaus' answer is the better option.
Original Answer
One way to do this would be to use a custom ModelForm in admin. This form can override the required field to make it editable. Thereby you retain editable=False everywhere else but Admin. For e.g. (tested with Django 1.2.3)
# models.py
class FooModel(models.Model):
first = models.CharField(max_length = 255, editable = False)
second = models.CharField(max_length = 255)
def __unicode__(self):
return "{0} {1}".format(self.first, self.second)
# admin.py
class CustomFooForm(forms.ModelForm):
first = forms.CharField()
class Meta:
model = FooModel
fields = ('second',)
class FooAdmin(admin.ModelAdmin):
form = CustomFooForm
admin.site.register(FooModel, FooAdmin)
Add the fields you want to display on your admin page.
Then add the fields you want to be read-only.
Your read-only fields must be in fields as well.
class MyModelAdmin(admin.ModelAdmin):
fields = ['title', 'author', 'published_date', 'updated_date', 'created_date']
readonly_fields = ('updated_date', 'created_date')
You could also set the readonly fields as editable=False in the model (django doc reference for editable here). And then in the Admin overriding the get_readonly_fields method.
# models.py
class MyModel(models.Model):
first = models.CharField(max_length=255, editable=False)
# admin.py
class MyModelAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
return [f.name for f in obj._meta.fields if not f.editable]
With the above solution I was able to display hidden fields for several objects but got an exception when trying to add a new object.
So I enhanced it like follows:
class HiddenFieldsAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
try:
return [f.name for f in obj._meta.fields if not f.editable]
except:
# if a new object is to be created the try clause will fail due to missing _meta.fields
return ""
And in the corresponding admin.py file I just had to import the new class and add it whenever registering a new model class
from django.contrib import admin
from .models import Example, HiddenFieldsAdmin
admin.site.register(Example, HiddenFieldsAdmin)
Now I can use it on every class with non-editable fields and so far I saw no unwanted side effects.
You can try this
#admin.register(AgentLinks)
class AgentLinksAdmin(admin.ModelAdmin):
readonly_fields = ('link', )