I need some help with my Django-admin (for golf project)

Hello community, I’m new to Django so let me know if there are any references I make that makes no sense and I will provide more specific information. :slight_smile:

I’ll start with the problem and afterward how I’ve setup my project so far:
For all the holes I’ve connected to a golfcourse, in django-admin I want to add a score for each hole that the user I choose have scored. But right now I have to add each score hole by hole instead of having a way to list all the 18 holes that are connected to the course. Of course I can make this just a static site, but with the attempt to learn more Django I felt like it was a OK difficulty to start with.

I have added the models (I think is needed): all the users(players), add the years we have played, the golf course we play (for now this has only been one course, but might change in the future), the holes connected to that golfcourse (hole number, hole index, and par for that hole), and also a scorecard where I can select one of the user, what year he played, what course and what he scored for each hole.

This is how my model looks now:
**imports not displayed

class User(models.Model):
    """
    User Model. Users will only be added by the Admin.
    """
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    user_bio = models.TextField(max_length=400)
    user_hcp = models.DecimalField(
        max_digits=5, decimal_places=2,
        validators=[MinValueValidator(Decimal('-3.00')),
                    MaxValueValidator(Decimal('54.00'))])

   def __str__(self):
        return f"{self.first_name} {self.last_name}"

class Year(models.Model):
    """
    Choices of what Year the Competition was played.
    """
    year = models.IntegerField()

   def __str__(self):
        return str(self.year)

class GolfCourse(models.Model):
    """
    GolfCourse Model. Information about the course.
    """
    name = models.CharField(max_length=100)

   def __str__(self):
        return self.name

class Hole(models.Model):
    """
    Hole Model. Connects with the GolfCourse
    """
    course = models.ForeignKey(GolfCourse, on_delete=models.CASCADE)
    hole_number = models.PositiveIntegerField(validators=[MinValueValidator(1)])
    hole_index = models.IntegerField(validators=[MinValueValidator(1)])
    hole_par = models.PositiveIntegerField()

   class Meta:
        unique_together = ('course', 'hole_number')

   def __str__(self):
        return str(self.hole_number)

class Scorecard(models.Model):
    """
    Scorecard to connect a User, what year and course he played, and what he 
    """
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    year_played = models.ForeignKey(Year, on_delete=models.CASCADE)
    course = models.ForeignKey(GolfCourse, on_delete=models.CASCADE)
    shots_made = models.PositiveIntegerField(default=1,
        validators=[
            MinValueValidator(1),
            MaxValueValidator(18)]
    ) **# would it be better to have shots made as a ManyToManyField?**

Also in my admin.py I’ve added this to make it easier when I add a course:

class ChoiceInline(admin.StackedInline):
    """Add 18 extra fields when adding a GolfCourse."""
    model = Hole
    extra = 18

class GolfCouseAdmin(admin.ModelAdmin):
    """Connected to ChoiceInline"""
    fieldsets = [
        ("Golfcourse", {"fields": ["name"]}),
    ]
    inlines = [ChoiceInline]

So based on the problem above, how am I able in django-admin to have it so when I click on “Scorecards” → Add Scorecard, I can choose What user, what Year he played, What Course he played (then best case scenario get a list of all the holes, and another field where I can manually type in that persons score).

Thankful for any help, and I’m excited to learn more Django :smiley:

Side note: When posting code here, enclose the code between lines of three backtick - ` characters. This means you’ll have a line of ```, then your code, then another line of ```. This forces the forum software to keep your code properly formatted. (I’ve taken the liberty of fixing your original post for you.)

The short answer is, you don’t. That’s not the purpose or intent of the Django admin.

Please read the first two paragraphs in the docs at The Django admin site | Django documentation | Django.

The type of UI/UX that you are describing here is going to be a lot easier to implement in your own views. (Now, you might be able to write enough code to get the admin to be reasonably close to what you’re describing here - but at that point, you’ve done more work than if you had just started out from scratch without it.)

1 Like

Thank you for your answer and input @KenWhitesell! :slight_smile:

So does the models look OK for this to be done? And should I try to make it with ModelForm and then in my view connect that with a CreateView? Since there are FK involved. I want to choose what user from my database, and I also want to have the data (all the holes) populate when I choose what course and then be able to write scores for each hole. I have some trouble to wrap my head around how to connect it all.

Thanks again for your answer.

You need to look at this for more than just the data-entry requirements. Designing your models should be done in their entirety based upon everything you can forsee doing with this data. What are you going to do with it after you’ve collected it? You want to design the models with that in mind as well.

Since it’s a golf project the data will be typed in once and after that only displayed in different templates. So what I’m basically trying to do is to have a quick way to manually add all the scores from each player, each year and then display them. Since we play in teams by two I’ve also added a “Team” model. From there I want to be filter upon year, user, team etc.

I could manually add score hole by hole, but I want to have one form that gets all the holes that are attached to the course we played, and choose what user that had that score and what year, all in one view. If that makes sense.

Thanks again for replying :slight_smile:

I’ve made some updates to the model as well:

class User(models.Model):
    """
    User Model. Users will only be added by the Admin.
    """
    name = models.CharField(max_length=200, verbose_name="Namn")
    user_email = models.EmailField(max_length=200, null=True)
    user_bio = models.TextField(max_length=400,
                                verbose_name="Bio")
    user_hcp = models.DecimalField(
        max_digits=5, decimal_places=2,
        validators=[MinValueValidator(Decimal('-3.00')),
                    MaxValueValidator(Decimal('54.00'))],
                    verbose_name="HCP")
    user_image = models.ImageField(upload_to='uploads/user', 
                                   default='uploads/default/user.png',
                                   verbose_name='Profilbild')

    def __str__(self):
        return self.name

class Year(models.Model):
    """
    Choices of what Year the Course was played.
    """
    year = models.IntegerField(verbose_name="År")

    def __str__(self):
        return str(self.year)

class GolfCourse(models.Model):
    """
    GolfCourse Model. Information about the course.
    """
    name = models.CharField(max_length=100,
                            verbose_name="Bana")
    golf_course_par = models.PositiveIntegerField(default=72)

    def __str__(self):
        return self.name

class Hole(models.Model):
    """
    Hole Model. Connects with the golfcourse
    """
    course = models.ForeignKey(GolfCourse, on_delete=models.CASCADE,
                               verbose_name="Bana")
    hole_number = models.PositiveIntegerField(validators=[MinValueValidator(1)],
                                              verbose_name="Hål")
    hole_index = models.IntegerField(default=0, verbose_name="Index")
    hole_par = models.PositiveIntegerField(verbose_name="Par")
    hole_length = models.PositiveIntegerField(verbose_name="Meter")

    class Meta:
        unique_together = ('course', 'hole_number')

    def __str__(self):
        return str(self.hole_number)
    
class GolfRound(models.Model):
    """Adding a round to a user"""
    course = models.ForeignKey(GolfCourse, on_delete=models.CASCADE, null=True,
                               verbose_name="Bana")
    round_name = models.CharField(max_length=40, default=None,
                                  verbose_name="Namn på rundan")
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Spelare")
    year = models.ForeignKey(Year, on_delete=models.CASCADE, verbose_name="År")

    def __str__(self):
        return f"{self.round_name} - {self.course.name}"

Then I added in my forms.py:

class ScoreFormTest(ModelForm):
    """Test"""
    class Meta:
        model = Scorecard
        fields = ['shots_made']

And with a DetailView:

class RoundDetail(DetailView):
    """Detail and Update Round/Score"""
    model = GolfRound
    context_object_name = 'round'
    template_name = 'info/round_detail.html'
    success_url = reverse_lazy('info:round_list')


    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['holes'] = Hole.objects.filter(course_id=self.object.course.pk)
        context['score'] = Scorecard.objects.filter(golf_round_id=self.object.pk).select_related('hole')
        context['form'] = ScoreFormTest(instance=self.object)
        context['total_score'] = Scorecard.objects.filter(golf_round_id=self.object.pk).aggregate(total_score=Sum('shots_made'))
        return context

This now render all the holes and I have a field to type in an integer value in my template that looks like this:

<div class="container">
    
    <h2>Användare: {{round.user}}</h2>
    <h3>År: {{round.year}} </h3>
    <div class="row">
        <div class="col-md-5">
           <form method="post">
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th>Hål</th>
                        <th>Par</th>
                        <th>Meter</th>
                        <th class="text-center">Antal Slag</th>
                    </tr>
                </thead>
                <tbody>
                    {% for hole in holes %}
                    <tr>
                        <td>{{hole.hole_number}}</td>
                        <td>{{hole.hole_par}}</td>
                        <td>{{hole.hole_length}}</td>
                        <td class="text-center">
                            {{ form }}
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
            {% csrf_token %}
            <button type="submit" class="btn btn-outline-success">Spara Resultat</button>
        </form> 
                
        </div>
    </div>
</div>    

But the only thing is I can’t figure out how to save each field in that form. Not sure if you’re able to solve this in the views.

I would think about taking this in a different direction.

First, I’ll start off assuming that GolfRound is a model for tracking all 18 holes of play by an individual on a course on a given date.
(Side note, it is possible that a person plays the same course twice in a day - you may want to add a “Start time” field for this model.)

There are three different fundamental ways that you can handle storing the scores from the individual holes.

  • You could add 18 fields to this model, one for each hole. (I don’t particularly recommend this - it makes writing some queries particularly difficult, such as “When did I have that hole where I scored a 17?”) (Actually, I don’t want to remember that, even though we all had a good laugh.)

  • You could create a related model, “HoleScore”, having three fields. These fields would be an FK to GolfRound, an FK to Hole, and an integer field for score. The view handling the selection of the User and GolfCourse can then create the instances of HoleScore to be presented in the score entry form.

  • You could add either a JSONField or an ArrayField to your GolfRound model to hold the scores for a round.

(Again, I don’t particularly suggest the first option, I’m presenting it only for completeness.)

For the actual representation of the per-hole entry fields, I would suggest using a formset. The precise implementation of which will depend a little upon which of these options you select. But for the most part, since you can count on there being 18 holes of data to be entered, it’s all going to be pretty straight-forward.