Thank you! Your feedback has been delivered
Thank you! Your feedback has been sent

Creating and saving a form using Django (preferably using django-crispy-forms but not mandatory)

I need to create a form where users can vote for the winner of each fight.

My models are:

class Fight(TimeStampedModel):
    event = models.ForeignKey(Event, null=True)
    fighter = models.ForeignKey(Fighter, related_name='%(app_label)s_%(class)s_fighter', null=True)
    fightstatus = models.ForeignKey('fights.FightStatus', null=True)
    opponent = models.ForeignKey(Fighter, related_name='%(app_label)s_%(class)s_opponent', null=True)

class FightChoice(TimeStampedModel):
    fight = models.ForeignKey(Fight)
    fighter = models.ForeignKey(Fighter, related_name='%(app_label)s_%(class)s_fighter', null=True)
    loser = models.ForeignKey(Fighter, related_name='%(app_label)s_%(class)s_lose', null=True)
    howitended = models.ForeignKey('fights.HowItEnded', null=True)
    points = models.IntegerField(blank=True, null=True)

class UserVote(TimeStampedModel):
    event = models.ForeignKey('events.Event')
    fightchoice = models.ForeignKey('fights.FightChoice')
    user = models.ForeignKey('users.User')

To help you visualize the relation between the fields and the data I've wrote some data:

**Fight table**

event_id  fighter_id  opponent_id
=======   =======     ==========
1          1               2

**Fightchoice table**

fight_id  fighter_id  howitended_id points
======    ========    ===========   =====
1         1            1            10     
1         2            1            20
1         1            2            30
1         2            2            40

**Fighter table**

id  name
=  =====
1  Vitor Belfort
2  Carlos Condit

**howitended table**

id  name
=  =====
1  KO
2  Submission

Now with the following data the form show look something like that (brackets are radio button):

Vitor Belfort               vs    Carlos Condit
ko         [  ] 10 pts                    KO [  ] 20 pts
submission [  ] 30 pts           Submisison  [  ] 40 pts

Then when they submit it should save to the fightchoice_id, event_id and user_id (request.user.id) to the UserVote table

Thank you and fell free to ask me any questions but I'm really new with Django so keep that in mind.

User Gravatar

ymorin007

Posted Jun 19 2014 12:36 UTC

$100


  • Assigned To omab
  • Solved
  • django
    python
  • 1296 Views

13 Replies


@ymorin007, I've made a simple application with a similar models layout (not quite the same since your post had some missing models definition), but it should be enough to get you started.

You can see the key parts in this gist https://gist.github.com/omab/fc53410609ef062d214d

You can also download the full application from http://matiasaguirre.net/vote.tar.gz

User Gravatar

omab

Posted Jun 19 2014 15:13 UTC

Thanks @omab

Very impress. I will try to implemented the code and let you know ASAP.

User Gravatar

ymorin007

Posted Jun 19 2014 15:23 UTC

in forms.py

Under class FightChoiceField()

How can I change the kwargs['queryset'] = FightChoice.objects.all()

to only select the fight(s) for the event in that example where event_id=1

Also I need to change the url param to be event_id and not fight_id since I need to show not only 1 fight but all the fights for that event.

User Gravatar

ymorin007

Posted Jun 19 2014 16:54 UTC

Hi @ymorin007,

The queryset there is just to pass it to the parent class, it's executed and shouldn't be the one you change. I've reviewed the code again and made a simplification, the iterator class is not needed.

I've used fight_id because it's a simple example, you can easily change that to be event_id.

I've updated the gist and the vote.tar.gz to reflect the mentioned changed.

User Gravatar

omab

Posted Jun 19 2014 17:57 UTC

Hi @omab,

I've copied the changes but now the form returns nothing and I've check and self.choices = FightChoice.objects.filter(fight__event_id=event_id) returns the correct values.

I've only changed from your code VoteForm(fight_id) to VoteForm(event_id)

What I'm missing here?

You can see my code at https://gist.github.com/ymorin007/49b4edfeab5c816d583b

User Gravatar

ymorin007

Posted Jun 20 2014 8:17 UTC

@ymorin007, there are a few issues in that code:

  1. FightVote doesn't have a fightchoice reference in it but your original post shows that and I've based my code on that reference.
  2. The VoteForm in your gist references to UserVote instead of FightVote and also references a fightchoice field which is missing from the model at the moment (here https://gist.github.com/ymorin007/49b4edfeab5c816d583b#file-forms-py-L29-L30).

I think this will work if you add the fightchoice reference to your FightVote model, and change the model reference in the VoteForm.

User Gravatar

omab

Posted Jun 20 2014 9:23 UTC

I forgot to add the UserVote in the gist but it does exist in my users/models.py

class UserVote(TimeStampedModel):
    event = models.ForeignKey('events.Event')
    fightchoice = models.ForeignKey('fights.FightChoice')
    user = models.ForeignKey('users.User')

FightVote is for something else and is not related to voting.

User Gravatar

ymorin007

Posted Jun 20 2014 9:36 UTC

@ymorin007, my bad, the gist wasn't properly updated, it's now https://gist.github.com/omab/fc53410609ef062d214d (Revision 4).

The problem is the set_event() method in FightChoiceField, the methods needs to set the self.queryset attribute, not self.choices.

So, basically in your gist, change this line https://gist.github.com/ymorin007/49b4edfeab5c816d583b#file-forms-py-L18 to be self.queryset instead of self.choices.

User Gravatar

omab

Posted Jun 20 2014 10:21 UTC

Perfect. Now it shows the fights in the template.

But if I have more then 1 fight it shows like this in my template (see image).

enter image description here

I want it to be one selection per fight not per event. Thanks.

Should be more like :

**Alexander Gustafsson vs Alistair Overeem**

 [] Alexander Gustafsson - Submission [600]
 [] Alexander Gustafsson - KO/TKO [600]
 [] Alistair Overeem - Submission [600]
 [] Alistair Overeem - KO/TKO [200]

**Carlos Condit vs Vitor Belfort**

 [] Carlos Condit - Submission [500]
 [] Carlos Condit - KO/TKO [100]
 [] Vitor Belfort - Submission [400]
 [] Vitor Belfort - KO/TKO [300]
User Gravatar

ymorin007

Posted Jun 20 2014 10:53 UTC

@ymorin007, that's a new requirement, to solve that I would suggest using multiple forms in your view, I've updated the gist with the changes.

The key part is the view here https://gist.github.com/omab/fc53410609ef062d214d#file-views-py-L9-L28 and the template https://gist.github.com/omab/fc53410609ef062d214d#file-template-html-L9-L13.

To make that work I've also had to switch the form back to use the fight reference instead of the event reference to filter the choices, which make sense since you want the choice to be per-fight, not per-event.

This is how it looks in my small demo

votes per fight

User Gravatar

omab

Posted Jun 20 2014 11:14 UTC

Thanks @omab,

But with the changes the template return nothing inside the {% for fight, form in forms %}

{{ forms|length }} = 0

But I can tell you that

event = Event.objects.get(pk=event_id) returns 1 event

and

event.fights.all() returns 2 fights.

I've also updated my gist with the latest code you provided me.

https://gist.github.com/ymorin007/49b4edfeab5c816d583b

Thanks again.

User Gravatar

ymorin007

Posted Jun 20 2014 15:05 UTC

@ymorin007, the problem is that your view is passing form in your context_dict, but the template expects forms.

Also the code inside the if request.method == 'POST' is not correct, check my version at https://gist.github.com/omab/fc53410609ef062d214d#file-views-py-L18-L26, you will see that it's a loop around the forms list and saves each form.

User Gravatar

omab

Posted Jun 20 2014 15:06 UTC

Solution

This didn't solve your task? Get your own custom solution.

Thanks a million. Works now.

I will add more tasks probably related to the same topic so I hope to work with you again.

User Gravatar

ymorin007

Posted Jun 21 2014 7:42 UTC

Add a reply

By posting a reply on CodersClan you agree to our Terms & Conditions