everyvote / everyvote_mini

Election information app for university student governments and other organizations
www.everyvote.org
1 stars 0 forks source link

Setup Showing to only show candidates who are running for a selected office in a selected election #27

Closed mitchdowney closed 11 years ago

mitchdowney commented 11 years ago

Please scroll to the last comments to understand this issue.

image

scvnc commented 11 years ago

A route should be set up that provides this modified election view:

/election/:e_id/:o_id

where :e_id is the election id and :o_id is the office id.

Down the road we can support prettier urls

mitchdowney commented 11 years ago

I'd like to learn more about how this would be done.

The current urls.py for showing the election page is:

# SHOW ELECTION - RANDOM CANDIDATES
    url(r'^elections/(?P<pk>\d+)/$', ElectionDetailView.as_view(
        model=Election,
        template_name='election_detail.html',
        context_object_name='election'),
        name='election_detail',
        ),

And the views is just (minus your blocked_users function):

class ElectionDetailView(DetailView):
    model = Election
    context_object_name = "election"

I don't know how to type the urls.py so that it accepts a pk for the election, and a different pk for the office. Maybe something with changing slug_field?

arocks commented 11 years ago

There are many ways to achieve this. The cleanest way to do this is to implement the query logic in models and override the get_queryset within the DetailView.

Within models.py add a method to Election class to accept a candidate id and office id and return the filtered query set. Just filtering the candidate_set based on office should work. You can debug this in your shell or writes tests to ensure it works.

Next, add a new DetailView in views.py (possibly mapped with the url regex url(r'^elections/(?P<pk>\d+)/office/(?P<office>\d+)/$',) and override the get_queryset as mentioned in the docs.

mitchdowney commented 11 years ago

Thank you arocks!

Well I tried for a few hours with your suggestion, and I can see you have shown the right answer, but I don't understand yet how to use get_queryset. The example in the docs is a ListView (I am using DetailView for the election page that lists its candidates on the page), and is not being passed a def from models.py. I might be too much of a noob to understand this right now.

I'm going to read the docs more closely over the next 2 days so I better understand get_queryset. Thanks again for pointing me in the right direction

mitchdowney commented 11 years ago

Notice that Office and Election each have a ForeignKey relationship with Constituency. This is because an Office is a position within a Constituency, but an Office is not within an Election.

Also, I insert candidates on an ElectionDetailView page by running a for loop on election.candidate_set.all. What I need to resolve this issue is basically run election.candidate_set, but filter it to return only candidates that are running for a specific office (for example, only show all President candidates who are running in this specific election).

urls.py

url(r'^elections/(?P<pk>\d+)/office/(?P<office_id>\d+)/$', ElectionOfficeDetailView.as_view(
    model=Election,
    template_name='election_detail_by_office.html'),
    name='election_detail_by_office',
    ),

models.py

class Constituency(models.Model):
    name = models.CharField(max_length=100)

class Office(models.Model):
    constituency = models.ForeignKey(Constituency)
    name = models.CharField(max_length=30)

class Election(models.Model):
    constituency = models.ForeignKey(Constituency)
    name = models.CharField(max_length=50)
    offices = models.ManyToManyField(Office, blank=True, null=True)

    def get_office_candidates(self, office_id):
        office_candidates = self.candidate_set.filter(office_id=office_id)

        return office_candidates

class Candidate(models.Model):
    user = models.ForeignKey(UserProfile)
    election = models.ForeignKey(Election)
    office = models.ForeignKey(Office)

views.py

class ElectionOfficeDetailView(DetailView):
    model = Election
    context_object_name = "election"

    def get_queryset(self):
        ???
        ???

Is the office_candidates function in models.py correct?

My biggest problem is I am confused about how I should override get_queryset to use get_office_candidates() in the views.py, I am not sure how to apply the example given in the docs to solve this problem:

example def get_queryset from docs

class PublisherBookList(ListView):

    template_name = 'books/books_by_publisher.html'

    def get_queryset(self):
        self.publisher = get_object_or_404(Publisher, name=self.args[0])
        return Book.objects.filter(publisher=self.publisher)

I am sorry for my ignorance. This is the last and biggest issue to resolve for this app to be ready for user testing. Do you have any suggestions for how to solve this?

suriya commented 11 years ago

get_queryset is useful only if you want DetailView to limit its choices of Election objects.

What you want to do is add a queryset of Candidate objects to your context so that the template can use it.

class ElectionOfficeDetailView(DetailView):
    model = Election
    context_object_name = "election"

    def get_context_data(self, **kwargs):
        context = super(ElectionOfficeDetailView, self).get_context_data(**kwargs)
        # At this point, the context contains the 'election' object
        election = context['election']
        # Add the 'office_candidates' to the context and use in the
        # template.
        #   {% for candidate in office_candidates %}
        #       candidate
        #   {% endfor %}
        context['office_candidates'] = election.get_office_candidates(kwargs['office_id'])
        return context
mitchdowney commented 11 years ago

Wow, thank you Suriya. Your explanation is very clear, but I tried it and am receiving this error:

KeyError at /elections/1/office/1 ...in get_context_data

Can you help me understand why that error is happening? I apologize, I have only been coding since this July, so there are many things I don't understand...

urls.py

url(r'^elections/(?P<pk>\d+)/office/(?P<office_id>\d+)/$', ElectionOfficeDetailView.as_view(
    model=Election,
    template_name='election_detail_by_office.html',
    context_object_name='election'),
    name='election_detail_by_office',
    ),

models.py

class Election(models.Model):
    constituency = models.ForeignKey(Constituency)
    name = models.CharField(max_length=50)
    offices = models.ManyToManyField(Office, blank=True, null=True)

    def get_office_candidates(self, office_id):
        office_candidates = self.candidate_set.filter(office_id=office_id)

    return office_candidates

and I used exactly your code for views.py

Thank you very much again. This issue has been vexing me for a while.

suriya commented 11 years ago

There needs to a minor change.

--- a.py    2013-09-06 11:48:01.555280223 +0530
+++ b.py    2013-09-06 13:33:10.901188387 +0530
@@ -11,5 +11,5 @@
         #   {% for candidate in office_candidates %}
         #       candidate
         #   {% endfor %}
-        context['office_candidates'] = election.get_office_candidates(kwargs['office_id'])
+        context['office_candidates'] = election.get_office_candidates(self.kwargs['office_id'])
         return context

Here's the correct version (I hope)

class ElectionOfficeDetailView(DetailView):
    model = Election
    context_object_name = "election"

    def get_context_data(self, **kwargs):
        context = super(ElectionOfficeDetailView, self).get_context_data(**kwargs)
        # At this point, the context contains the 'election' object
        election = context['election']
        # Add the 'office_candidates' to the context and use in the
        # template.
        #   {% for candidate in office_candidates %}
        #       candidate
        #   {% endfor %}
        context['office_candidates'] = election.get_office_candidates(self.kwargs['office_id'])
        return context
mitchdowney commented 11 years ago

It works, amazing! Thank you Suriya, this is a huge help. I was stumped on this for a few days.