问题
I have been working on this one for a few days and have re-worked how i'd like to handle this functionality on my fantasy sports website:
Objective: limit the number of players allowed on a fantasy owner's roster.
Django out-of-the-box User = Owner
# models.py
class Player(models.Model):
player_full = models.CharField(max_length=50)
player_owner = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
image = models.ImageField(default='default_player.jpg', upload_to='player_pics')
player_unit_value = models.PositiveSmallIntegerField(default=1, validators=[MinValueValidator(1),
MaxValueValidator(1),])
And here is the class on views.py which i suppose is how the form is functioning. There is no forms.py in this area of my project.
# views.py
class PlayersUpdateView(LoginRequiredMixin, UpdateView, Player):
model = Player
fields = []
template_name = 'blog/player_form.html' # <app>/<model>_<viewtype>.html
context_object_name = 'players'
# this def makes sure only the post author can edit the post
def test_func(self, **kwargs):
# this makes sure it is the exact post we are updating
player = self.get_object()
if self.request.user == player.player_owner:
return True
return False
def form_valid(self, form):
form.instance.player_owner = self.request.user
return super().form_valid(form)
So I have the functionality working for "adding" a player to your roster where it verifies you're signed in then --when submitted-- the Player.player_owner field updates from None
to the User. What I'd like it to do is --when a User tries to add a new player to their roster, the system checks how many players the User already has (aka how many times that User is listed as the player_owner when running through the entire database of Player model) and spits out an error if the player already has the max of 15 players. Otherwise it allows the "adding" to go through.
If you notice on the Player model I also have a field called player_unit_value
which is universally stuck at int 1. I was able to use this field to produce total players on a User's roster number on the team's roster page. So on each owner's roster page it shows the total number of players they have. Here is the views.py class that I used to pull that off (thanks to @ruddra for that code):
class UserPlayerListView(ListView):
model = Player
template_name = 'blog/user_players.html' # <app>/<model>_<viewtype>.html
context_object_name = 'players'
paginate_by = 20
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Player.objects.filter(player_owner=user).order_by('-player_sal_19_20')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
player_list = list(context['players']) # evaluates the query
context['sum_of_sal_19_20'] = sum([x.player_sal_19_20 for x in player_list]) # pythonic sum calculation
context['player_count'] = sum([x.player_unit_value for x in player_list])
context['players'] = player_list
return context
Then I could use the html tag {{ player_count }}
So this proves that that number (counting the player_unit_value
which is always 1 for every player who has the User listed in their player_owner field) can be calculated and displayed on the front end. What I want to do is access that number when a User is trying to add a player and --if that number is above 14-- deny the acquisition. I just havent figured out how to work that functionality on the PlayerUpdateView
(which is working as a form) rather than the UserPlayerListView
.
I've seen a lot of talk on here about how to have custom form validation like this I'd need to have an actual forms.py file for this area of the project. I have a forms.py file associated with my users area which handles the user profile form (not the same as the user roster page). Can I set up a forms.py file retroactively for a form thats already working? I also messed around with trying to create my own validators.py file and use custom validators on the player_owner field with no success.
Edited code:
Views.py
def form_valid(self, form):
if self.request.method == 'POST':
form = AddPlayerForm(self.request.POST)
if form.is_valid():
form.save(commit=False)
current_user = self.request.user
if Player.objects.filter(player_owner=current_user).count() > 14:
messages.info(self.request, f'Your roster is full! Player not added.')
return redirect('profile')
else:
form.instance.player_owner = self.request.user
return super().form_valid(form)
Forms.py
class AddPlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = []
note:
for some reason, I had to include "self" in front of all the uses of request on views.py in order to avoid an error. Also, Before I tried to add this form validation, I was able to edit the Player model via these class-based forms on the view using this 'PlayerUpdateView' class form and a separate 'PlayerDropView' class form. Both of those respectively accomplished one function when initiated: switch the selected Player model field 'player_owner' from None to the signed in User, or vice versa. So I was able to pass old info into a form and then post it as an update,I just never had to dig into it until i need to start validating.
So As it stands now: The code does restrict a User to 15 or less players on their roster, but now when a player is added, a blank instance of a player is created on the backend and assigned the User as the player_owner, while the player's player_owner field remains None.
Final Edit:
@Josh provided this solution. See his notes in the answer:
views.py
def player_update(request, id,):
player = Player.objects.get(id=id)
current_user = request.user
if Player.objects.filter(player_owner=current_user).count() <= 14:
player.player_owner = current_user
player.save()
messages.success(request, f'Player added!')
return redirect('blog-players')
else:
messages.warning(request, f'Your roster is full! Player not added.')
return redirect('blog-players')
def players(request):
context = {
'players': Player.objects.all(),
}
return render(request, 'blog/players.html', context)
Question: For some reason when I try to use the else: message.error
the red outline does not show up on output. Why? Also, I can't make my re-direct go to the User's specific roster page. Why?
urls.py
path('user_players/<str:username>/', UserPlayerListView.as_view(), name='user-players'),
path('players/<id>/updating/', views.player_update, name="player-updating")
html on players.html
<a class="dropdown-item" href="{% url 'player-updating' player.id %}">Sign</a>
回答1:
You could do your logic in views.py or within forms.py.
The below should work, though haven't tested it personally.
views.py
if request.method == 'POST':
form = forms.AddPlayer(request.POST)
if form.is_valid():
instance = form.save(commit=False)
current_user = request.user
if Player.objects.filter(user_id=current_user).count() > 14:
# do something here, such as return error or redirect to another page
else:
instance.save()
or in forms: forms.py
class AddPlayerForm(forms.ModelForm):
class Meta:
model = Player
fields = ['name', 'number']
def clean(self):
current_user = request.user
if Player.objects.filter(user_id=current_user).count() <= 14:
pass
else:
raise ValidationError('Your team is full!')
Edit: Update.
So this is how I would do it. I'm not a pro at this, so I'm sure there's a better way, but this will do the trick and isn't too bulky.
So big picture: have a page with all the "Players" on it (or a paginated page), then for each player, have a link that runs some logic which checks the roster of the current user, and if 14 or fewer, adds that to the list.
urls.py
path('player_list', views.player_list, name="playerlist"),
path('player/<id>/update', views.player_update, name="playerupdate"),
views.py
def player_list(request)
player_list = Player.objects.all()
return render(request, 'player_list.html', {'player_list': player_list}
def player_update(request, id):
player = Player.objects.get(id=id)
current_user = request.user
if Player.objects.filter(user_id=current_user).count() <= 14:
player.player_owner = current_user
player.save()
return redirect('roster')
else:
return redirect('error')
player_list.html
{% for player in player_list %}
{{player.name}} -- <a href="/player/{{player.id}}/update"> Click here to claim this player!</a>
{% endfor %}
来源:https://stackoverflow.com/questions/62945436/class-based-view-sum-used-for-class-based-form-validation-without-forms-py