Dynamic form generation - python

I have a model for event invitations:
class EventInvitation(models.Model):
from_user = models.ForeignKey(User, related_name="inviters")
to_user = models.ForeignKey(User, related_name="invited")
text = models.CharField(max_length= 150)
event = models.ForeignKey(Event)
sent = models.DateTimeField(auto_now=True)
status = models.IntegerField(choices=INVITATION_STATI, default=0)
What I am trying to accomplish is to have a dynamic invitation form (NOT ModelForm), where the inviter has a selection of users, based on a query / people who CAN be invited by that particular user, and a selection of groups, also based on a query (the inviter has to be the owner of the group)... Since the EventInvitation is for a single user, I would then iterate through the selected users AND members of the group and create individual invitations for all of them. Any idea how I could generate this dynamic form?

As I understand, you want a logged in user, from an event page, click on a "Invite Others" button, which shows him a form, where he can select specific users and groups, specify some text, and click on "Send". Then your app should create many instances of the invitation (one per user) and send them, tracking the status of invitation. If this is correct, here are my suggestions for implementing this:
Using the following models will give you more control over your data (keeping the text only once, and allowing you to look up all users for a specific invitation):
class EventInvitation(models.Model):
inviter = models.ForeignKey(User, related_name="inviters")
event = models.ForeignKey(Event)
text = models.CharField(max_length= 150)
created = models.DateTimeField(auto_now=True)
class EventInvitationInvitee(models.Model):
event_invitation = models.ForeignKey(EventInvitation)
user = models.ForeignKey(User, related_name="invited")
status = models.IntegerField(choices=INVITATION_STATI, default=0)
Use a simple form like this:
from django.contrib.auth.models import User, Group
class InviteForm(forms.Form):
text = forms.CharField(widget=forms.Textarea)
users = forms.ModelMultipleChoiceField(queryset=User.objects.all())
groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all())
def __init__(self, user, *args, **kwargs):
super(InviteForm, self).__init__(*args, **kwargs)
self.fields['users'].queryset = User.objects.filter( ... )
self.fields['groups'].queryset = User.objects.filter( ... )
Replace ... with your code for filtering the correct groups and users.
And a view in this fashion:
def invite(request, event_id):
event = get_object_or_404(Event, pk=event_id) # you can check if your user is allowed to access this event here
if request.method == 'POST':
form = InviteForm(request.user, request.POST)
if form.is_valid():
invitation = EventInvitation.objects.create(inviter=request.user, event=event, text = form.cleaned_data['text'])
users = set()
for user in form.cleaned_data['users']:
users.add(user)
EventInvitationInvitee.objects.create(event_invitation=invitation, user=user)
for group in form.cleaned_data['groups']:
for user in group.user_set.all():
if user not in users:
users.add(user)
EventInvitationInvitee.objects.create(event_invitation=invitation, user=user)
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = InviteForm(request.user)
return render_to_response('invite.html', {'form': form})
Update: You can also create a dynamic form in a more pythonic way like this:
def get_invite_form(user):
class InviteForm(forms.Form):
text = forms.CharField(widget=forms.Textarea)
users = forms.ModelMultipleChoiceField(queryset= ... )
groups = forms.ModelMultipleChoiceField(queryset= ... )
return InviteForm
replacing ... with a queryset using the user parameter, and later use get_invite_form(request.user)(request.POST) and get_invite_form(request.user)() instead of InviteForm().

Related

Django query - connect User Class with OnetoOnefield class for Telegram Bot

I am having an issue with querying an attribute from a custom class connected with the user class.
My Goal: I want to send messages on my website per mouseclick via telegram bot to my phone (messaging works) - For this I need the USER´S CHAT ID
As there are multiple users I stored the chat_id in a new model I created, which is linked to User model with a onetoonefield:
User = get_user_model()
class TelegramProfile(models.Model):
name = models.CharField(default="",max_length=200,blank=True,unique=True)
user = models.OneToOneField(User,on_delete=models.CASCADE,related_name="user_id")
telegram_chat_id = models.CharField(max_length=40,default="",editable=True,blank=True,unique=True)
def __str__(self):
return str(self.name)
My Usermodel is the built in Model:
class User(auth.models.User, auth.models.PermissionsMixin):
def __str__(self):
return f"#{self.username}"
So in my views.py file I have the function on which I grab the message (another model class called Recipe - I identify it by a primary key) and and the chat_id which belongs to a specific user:
def bot(request,msg,chat_id,token=my_token):
bot=telegram.Bot(token=token)
bot.send_message(chat_id=chat_id, text=msg)
def home(request,pk):
recipe = get_object_or_404(Recipe,pk=pk)
user = request.user
chat_id = User.objects.get(TelegramProfile.telegram_chat_id,user) #with this query I try to grab the chat_id of the logged in user
ingredients = recipe.ingredients
ingredients = ingredients.split("<p>")
ingredients = "\n".join(ingredients)
ingredients = strip_tags(ingredients)
bot(request,ingredients,chat_id)
return render(request,"recipes/send_recipe.html")
So my question is:
-How do I make the query for the chat_id so that:
the chat_id of the specific logged in User will be called so I can insert it in the bot function to send the message to this specific id?
I am still new in django so I am still learning the queries, thanks so much in advance!!!
EDIT: Was able to solve it myself:
def send_recipe(request, pk):
try:
if request.method == "POST":
data_option = str(request.POST[
"testselect"]) # testselect is the select form in recipe_detail.html with which I select 1 of the 2 options
if data_option == "Ingredients":
recipe = get_object_or_404(Recipe, pk=pk)
recipe_name = recipe.name.upper()
ingredients = recipe.ingredients
ingredients = ingredients.split("<p>")
ingredients = "\n".join(ingredients)
ingredients = strip_tags(ingredients)
message = f"{recipe_name}: \n {ingredients}"
user = TelegramProfile.objects.get(
user=request.user) # use get instead of filter! filter returns the name but can t use it as object, with get I get the object and I can use the attributes on it!
chat_id = user.telegram_chat_id
bot(request, message, chat_id)
return render(request, "recipes/send_recipe.html")
elif data_option == "Ingredients & Description":
recipe = get_object_or_404(Recipe, pk=pk)
recipe_name = recipe.name.upper() # recipe name
ingredients = recipe.ingredients
ingredients = ingredients.split("<p>")
ingredients = "\n".join(ingredients)
ingredients = strip_tags(
ingredients) # we grab recipes ingredients and mute them, split at p tag, then add a new line and join them then remove tags
recipe_description = recipe.description
recipe_description = recipe_description.split("<p>")
recipe_description = "\n".join(recipe_description)
recipe_description = strip_tags(recipe_description)
message = f"{recipe_name}: \n {ingredients} \n \n {recipe_description}"
user = TelegramProfile.objects.get(user=request.user)
chat_id = user.telegram_chat_id
bot(request, message, chat_id)
return render(request, "recipes/send_recipe.html")
else:
return render(request, "recipes/create_telegram.html")
except Exception:
return render(request, "recipes/create_telegram.html")

How to filter the options that can be seen in a foreignkey field that has been put into a django form using drop down menu?

I have a model for Advertisements and I display a list of them on a page. The user is to click the advertisement that they want added to their profile. When a user clicks one of the advertisements they would like added they are redirected to a page that lets them add that advertisement object to their profile. However I would like to filter the advertisements in the drop down form displayed on that page to only show the advertisement object that they selected from the list. How would I filter the results to just one option (i.e the advertisement they selected.)
ad_update view
def ad_update(request, my_id):
obj = Advertisement.objects.get(id=my_id)
profile = get_object_or_404(Profile, user=request.user)
amount = getAddressAmountUSD(request.user.profile.advertisement.ethAddress)
if request.method == 'POST':
ad_form = AdvertisementUpdateForm(request.POST, instance=profile)
if ad_form.is_valid():
ad_form.save()
messages.success(request, f'Your account has been Updated!')
return redirect('profile')
else:
ad_form = AdvertisementUpdateForm(instance=profile)
context = {
'ad_form': ad_form,
'object': obj,
'amount': amount,
}
return render(request, 'users/advertisement_update.html', context)
form
class AdvertisementUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['advertisement']
model
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
ethAddress = models.CharField(max_length=42, default='')
advertisement = models.ForeignKey(Advertisement,
on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return f'{self.user.username} Profile'
ForeignKey is represented by django.forms.ModelChoiceField, which is a ChoiceField whose choices are a model queryset.
You can filter field options like below.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['foo'].queryset = ...
You can filter query set from view also like below.
form.foo.queryset = Foo.objects.filter(...)
I hope this will help you :)

How to implement PUT method in Django

How can I enable the user to generate only one instance of an object “bet” with a POST method and modify it through a PUT method (for example)
forms.py
class BetForm(forms.ModelForm):
team1_score = forms.IntegerField(min_value=0, max_value=15)
team2_score = forms.IntegerField(min_value=0, max_value=15)
match = forms.ModelChoiceField(queryset=Match.objects.only('id'))
class Meta:
model = Bet
fields = ('team1_score', 'team2_score', 'match')
models.py
class Bet(models.Model):
match = models.ForeignKey(Match, on_delete=models.CASCADE, related_name='+')
team1_score = models.PositiveIntegerField(default=0)
team2_score = models.PositiveIntegerField(default=0)
def __str__(self):
return (str(self.match))
views.py
def post(self, request):
form = BetForm(request.POST)
if form.is_valid():
form.save()
team1_score = form.cleaned_data.get('team1_score')
team2_score = form.cleaned_data.get('team2_score')
match = form.cleaned_data.get('match')
form = BetForm()
return redirect ('home')
args = {'form': form, 'team1_score': team1_score, 'team2_score': team2_score, 'match': match}
return render(request, self.template_name, args)
Enable the user to generate only one instance of an object “bet”...
For that, you want to add a user field to your Bet model. Here you will save a reference to the user making the request.
class Bet(models.Model):
user = models.ForeignKey(AUTH_USER_MODEL, related_name='bets', blank=True)
match = models.ForeignKey(Match, related_name='bets')
team1_score = models.PositiveIntegerField(default=0)
team2_score = models.PositiveIntegerField(default=0)
class Meta:
unique_together = ('user', 'match')
def __str__(self):
return (str(self.match))
Notice the unique_together option which makes sure a user can only create a single Bet instance for a given match.
modify it through a PUT method (for example)
Django does not automatically parse the body for PUT requests like it does for POST. Browsers normally issue POST request on forms submission. If you still want to solve it using PUT, check this post (pun intended).
Parsing Unsupported Requests (PUT, DELETE, etc.) in Django
My suggestion is to modify your post view so it accepts an optional parameter bet_id. This you can define in urlpatterns. The view would then look like this one. You retrieve the bet if bet_id is provided and pass it to the form. This way it understands the user is modifying it.
def post(self, request, bet_id=None):
if bet_id:
bet = Bet.objects.get(pk=bet_id)
form = BetForm(request.POST, instance=bet)
else:
form = BetForm(request.POST)
if form.is_valid():
bet = form.save(commit=False)
bet.user = request.user
bet.save()
# Do what you want here
Notice that we are not saving the form immediately (commit=False), so we could assign it to a user later on. This user is the logged in user from the request object.

get() returned more than one Friend -- it returned 2

I'm trying to create a django app in which one user can add other user as Friend. Here's what I did,
models.py,
class Friend(models.Model):
users = models.ManyToManyField(User)
current_user = models.ForeignKey(User, related_name='all', null=True)
views.py
# view for adding or removing friends
def change_friends(request, pk):
new_friend = User.objects.get(pk=pk)
data = Friend.objects.get(current_user=request.user)
frnds = data.users.all()
new_friend in frnds:
data.users.remove(new_friend)
else:
data.users.add(new_friend)
redirect(request.META['HTTP_REFERER'])
# Displaying frinends,
def following(request, id=None):
my_friend, created = Friend.objects.get_or_create(current_user_id=id)
all_friends = my_friend.users.all()
return render(request, 'all/follow.html', {'all_friends': all_friends})
This code was working fine until I added friends from 1 account only, but when I added several friends from several accounts it started showing an error get() returned more than one Friend -- it returned 2!.
How can we fix that? Thank You!
in change friend function this line of code change like this
new_friend = User.objects.filter(pk=pk).first()
Try this...
Delete all Friend instances in admin and change model to:
class Friend(models.Model):
users = models.ManyToManyField(User)
current_user = models.OneToOne(User, related_name='friend', on_delete=models.CASCADE, null=True)
then views should be:
# view for adding or removing friends
def change_friends(request, pk):
new_friend = User.objects.get(pk=pk)
friends = request.user.friend.users.all()
new_friend in friends:
request.user.users.remove(new_friend)
else:
request.user.users.add(new_friend)
redirect(request.META['HTTP_REFERER'])
# Displaying frinends,
def following(request, id=None):
my_friend, created = Friend.objects.get_or_create(current_user_id=id)
all_friends = my_friend.users.all()
return render(request, 'all/follow.html', {'all_friends': all_friends})
If you use many_to_many to record friends relationship,it should be better set model as:
current(OneToOne) users(ManyToMany)
If you use ForeignKey to record friends relationship,it should be better set model as:
current(ForeignKey) user(ForeignKey)
Update
If you can't change models anymore,just change code to:
def change_friends(request, pk):
new_friend = User.objects.get(pk=pk)
data = Friend.objects.filter(current_user=request.user)
has_user = False
for x in data:
if new_friend in x.users.all():
has_user = True
x.users.remove(new_friend)
if not has_user:
firend = Friend.objects.filter(current_user=request.user).first()
if friend:
friend.users.add(new_friend)
else:
friend = Friend.objects.create(current_user=request.user)
friend.users.add(new_friend)
redirect(request.META['HTTP_REFERER'])

Referral system like dropbox one

I want to implement a referral system to my system where registered users can invite other people by emailing their referral link (i.e. /register/referral/123123/) just like dropbox one ( and if a person signs up, the referrer gets additional bonus.
Currently I have implemented it this way:
Models:
class UserReferral(models.Model):
STATUS_INVITED = 1
STATUS_ACCEPTED = 2
STATUS_EXPIRED = 3
STATUS_CHOICES = (
(STATUS_INVITED, 'Invited'),
(STATUS_ACCEPTED, 'Accepted'),
(STATUS_EXPIRED, 'Expired'),
)
referrer = models.ForeignKey(User, related_name='referrers')
referred = models.ForeignKey(User, related_name='referred')
number = models.IntegerField()
status = models.IntegerField(choices=STATUS_CHOICES, default=STATUS_INVITED)
class Meta:
unique_together = (('referrer', 'referred'),)
def __unicode__(self):
return 'User %s referred %s' % (self.referrer.get_full_name(), self.referred.get_full_name())
#property
def referral_expired(self):
expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
return (self.status == self.STATUS_ACCEPTED or
(self.referred.date_joined + expiration_date <= datetime_now()))
Views:
This view is used by the registered users to send out new referral invites
#login_required
def invite_friends(request, template_name='accounts/invite_friends.html'):
if request.method == 'POST':
form = InviteForm(request.POST, user=request.user)
if form.is_valid():
emails = form.cleaned_data['emails']
for email in emails:
try:
user_referral = UserReferral.objects.get(referrer=request.user, referred__email=email)
except UserReferral.DoesNotExist:
random_username = ''.join(random.choice(string.ascii_uppercase) for x in range(6))
user = User.objects.create(username=random_username, password=email, is_active=False) # Dummy user to be overridden
user_referral = UserReferral.objects.create(referrer=request.user, referred=user, number=random.randint(10000, 99999))
send_mail('accounts/notifications/invite_friends', recipient_list=[email],
context={'user': request.user, 'number': user_referral.number})
messages.add_message(request, messages.SUCCESS, "Invites are sent.")
return redirect(reverse('profile_dashboard'))
else:
form = InviteForm(user=request.user)
return render(request, template_name, locals())
This is the URL where referred users can register, it basically calls the original register function with referral code, and check in the register view if the referral code is present, if so, it fetches the referred user instance from the UserReferral instance and populates the user data from the register form and saves that new user.
def referred_signup(request, referral_code):
user_referral = get_object_or_404(UserReferral, number=referral_code)
if user_referral.referral_expired:
raise Http404
response = register(request, referral_code=referral_code)
return response
So I create the dummy inactive 'referrer' User account every time the new invite is generated. And when on the registration time, I populate the names, password etc. from the user input form, and change the UserReferral instance status to ACTIVATED. Is there any better alternative to this one?

Categories