Doing a Multi-step form with Django Class-based CreateView - django

I have multiple class-based createviews. My goal is to link all createviews such that when I post the first createview, it will redirect me to the second createview where it will retrieve the data entered from first createview.
Does anyone know the solution to this?
The first createview (Step01) contains django-inline-formset-factory code which is similar to this code, while the rest (Step02 and Step03) contain basic code.
I have referred to this link, but the solution is based on using function-based view. Have also made attempt using Django's Form-Wizard, but handling django-inline-formset with it is still too complicated for me.
Why do I do this? So that each group level (i.e. general, staff, managerial) can access their respective view but not the view for other group level.
This is my current code:
models.py
class Model_Step01_Cart(models.Model):
cart_name = models.CharField(max_length=100)
class Model_Step01_CartItem(models.Model):
cart = models.ForeignKey(Profile)
item_name = models.CharField(max_length = 100)
item_quantity = models.FloatField(null = True, blank = True)
class Model_Step02_Staffnote(models.Model):
note_staff = models.TextField(max_length = 500, null = True, blank = True)
class Model_Step03_Managernote(models.Model):
note_manager = models.TextField(max_length = 500, null = True, blank = True)
forms.py
class Form_Step01_Cart(forms.ModelForm):
class Meta:
model = Model_Step01_Cart
fields = ["cart_name"]
class Form_Step01_CartItem(forms.ModelForm):
class Meta:
model = Model_Step01_CartItem
fields = ["cart", "item_name", "item_quantity"]
Formset_CartItem = forms.inlineformset_factory(
Model_Step01_Cart,
Model_Step01_CartItem,
form = Form_Step01_CartItem,
extra = 3
)
class Form_Step02(forms.ModelForm):
class Meta:
model = Model_Step02_Staffnote
fields = ["note_staff"]
class Form_Step03(forms.ModelForm):
class Meta:
model = Model_Step03_Managernote
fields = ["note_manager"]
views.py
class View_Step01(CreateView):
# contains formset_factory for "Model_Step01_Cart" and "Model_Step01_CartItem"
model = Model_Step01_Cart
fields = ["cart_name"]
# similar with the code displays here: https://medium.com/#adandan01/django-inline-formsets-example-mybook-420cc4b6225d
class View_Step02(CreateView):
# gets info from step 01, and adds staff's note
class View_Step03(CreateView):
# gets info from step 01 and 02, and adds manager's note.

Find my answer to split a single form across multiple views. You can configure the gist script to fit your requirement
For class-based-view map the success url of one view to other CreateView.as_view(model=myModel, success_url="/<path-to-view2/")

Related

Testing forms and widgets - Python

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)

Django admin list_display reverse to parent

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.

How do I represent two models on one page of form wizard?

I'm struggling to figure out how best to approach this.
I have two models that need to be represented on one page within a form wizard:
class BookingItem(models.Model):
assignedChildren = models.ManyToManyField('PlatformUserChildren', related_name = "childIDs", null=True)
quantity = models.PositiveSmallIntegerField(max_length=2,blank=True, null=True)
class PlatformUserChildren(models.Model):
child_firstname = models.CharField('Childs first name', max_length=30,blank=True, null=True)
The relationship between the models when presenting them on the page is NOT one-to-one. It is governed by quantity attribute and therefore there may be more BookingItems than PlatformUserChildren objects presented on the page (e.g. 4 BookingItems and 2 PlatformUserChildren objects). I loop through each object multiple times based on quantity.
I also need to bind to a queryset of the PlatformChildUser model based on the current logged in user.
My question: how do I best present these two models on the first page of my form wizard?
I have looked at inline_formsets, but they rely on foreign key relationships only.
I have tried a modelformset_factory for one model, and an additional identifier for the other model with some backend reconciliation later, but I'm stuck on how to get a queryset based on user in there
i have attempted the get_form_instance method but I'm not 100% sure if it supports querysets
finally, I have attempted overloading init however most of the examples are not based on form wizard, and supply external arguments.
My current (rather vanilla) code is below:
forms.py
class checkout_PlatformUserChildren(forms.ModelForm):
#activity_id = forms.IntegerField()
class Meta:
model = PlatformUserChildren
fields = ('child_age','child_firstname')
class Meta:
model = PlatformUserChildren
fields = ('child_age','child_firstname')
widgets = {
'child_firstname': SelectMultiple(attrs={'class': 'form-control',}),
'child_age' : TextInput(attrs={'class': 'form-control',}),
}
checkout_PlatformUserChildrenFormSet = modelformset_factory(
PlatformUserChildren,
form = checkout_PlatformUserChildren,
fields=('child_firstname', 'child_age'),
extra=1, max_num=5, can_delete=True)
views.py (done method not shown)
note: getUser is an external function that is currently working
checkoutForms = [
("assign_child", checkout_PlatformUserChildrenFormSet),
("address_information", addressInfo),
]
checkoutTemplates = {
"assign_child": "checkout/assign_child.html",
"address_information": "checkout/address_information.html",
}
class checkout(SessionWizardView):
def get_form_instance(self, step):
currentUser = getUser(self.request.user.id)
if step == 'assign_child':
self.instance = currentUser
return self.instance

django change formfield data

I am working with django-taggit (https://github.com/alex/django-taggit). To let a user add tags i use a formfield that I convert into the tags and add them.
However, when i try to load the template for editing. i get the taggit objects in my bar.
Now i want to convert those in a normal readable string again.
However, i can't seem to edit that field of the instance before passing it to my form.
The Form:
class NewCampaignForm(forms.ModelForm):
""" Create a new campaign and add the searchtags """
queryset = Game.objects.all().order_by("name")
game = forms.ModelChoiceField(queryset=queryset, required=True)
tags = forms.CharField(required=False)
focus = forms.ChoiceField(required=False, choices=Campaign.CHOICES)
class Meta:
model = Campaign
fields = ["game", "tags", "focus"]
exclude = ["owner"]
my model:
class Campaign(models.Model):
""" campaign information """
ROLEPLAY = "Roleplay"
COMBAT = "Combat"
BOTH = "Both"
CHOICES = (
(ROLEPLAY, "Roleplay"),
(COMBAT, "Combat"),
(BOTH, "Both"),
)
owner = models.ForeignKey(User)
game = models.ForeignKey(Game)
focus = models.CharField(max_length=15, choices=CHOICES)
tags = TaggableManager()
view:
def campaign_view(request, campaign_id):
campaign = get_object_or_404(Campaign, pk=campaign_id)
campaign.tags = "Some string"
new_campaign_form = NewCampaignForm(instance=campaign)
But when i try this i still get the taggit objects([]) in my inputfield instead of the "Some string"
How should i solve this
I am not sure how i overlooked this. But this works:
new_campaign_form = NewCampaignForm(instance=campaign, initial={"tags": "Some String"})
Excuse me, should have looked more and try better

django admin and inlines

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.