Referral system like dropbox one - python

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?

Related

Django pass user id before form is submitted

I am working on a Django-Tenant (Multi-Tenant) application. I am writing the script to create the subdomain. I am trying to get it to where created_by is set to the current users id that is logged in. How can I get current user ID to populate the created_by field?
views.py
class CreatePortal(View):
def get(self, request):
form = CreatePortalForm()
return render(request, "registration/create_portal.html", {"form": form})
def post(self, request):
form = CreatePortalForm(request.POST)
if form.is_valid():
getDomain = form.cleaned_data.get('name')
instance = form.save(commit=False)
tenant = Client(schema_name=getDomain, name=getDomain, created_by=**[NEED USER ID HERE]**)
tenant.save()
domain = Domain()
domain.domain = (getDomain + ".example.com:8000")
domain.tenant = tenant
domain.is_primary
domain.save()
with schema_context(tenant.schema_name):
instance.save()
redirect = 'http://' + getDomain + '.example.com:8000'
return HttpResponseRedirect(redirect)
return render(request, "registraton/create_portal.html", {"form": form})
forms.py
class CreatePortalForm(forms.ModelForm):
class Meta:
model = Client
fields = ["name"]
models.py
This is the line that I am working with models.py
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
I also get an error if it is not User type and if I do not pass an actual number for id, Field 'id' expected a number but got datetime.datetime
I have tried this as well:
user = request.user.id
tenant = Client(schema_name=getDomain, name=getDomain, created_by=user)
but then get this error:
Cannot assign "1": "Client.created_by" must be a "User" instance.
I am not sure how to pass the current logged in Users ID to populate the created_by form field. Any and all help is appreciated. I am still learning Django.

Submit a form only once in Django

I am trying to make the user submit a form only once. I have a /dashboard page which is shown after submitting the /petform. But, I want the user to submit the form only once after logging in and other times it should redirect to the /dashboard directly (or show a message that "form already submitted").
models.py
class PetFormData(models.Model):
abstract = True
name = models.CharField(max_length=100)
age = models.IntegerField()
breed = models.CharField(max_length=100)
amount_spent = models.CharField(max_length=100, choices=AMOUNT_CHOICES)
pincode = models.CharField(max_length=15)
services_required = models.CharField(max_length=100, choices=SERVICE_CHOICES)
auth_user_email = models.ForeignKey(User, on_delete=models.CASCADE)
form_submitted = models.BooleanField(default=False)
views.py
#login_required
def showformdata(request):
form = PetForm(request.POST)
if request.method == 'POST':
if not PetFormData.form_submitted and user == PetFormData.auth_user_email:
PetFormData.form_submitted = True
print(PetFormData.form_submitted)
if form.is_valid():
user = request.user
nm = form.cleaned_data['name']
age = form.cleaned_data['age']
breed = form.cleaned_data['breed']
am_sp = form.cleaned_data['amount_spent']
pin = form.cleaned_data['pincode']
ser_req = ','.join(form.cleaned_data['services_required'])
model_pet_form = PetFormData(name=nm, age=age, breed=breed, amount_spent=am_sp, pincode=pin,
services_required=ser_req, auth_user_email=user)
model_pet_form.save()
print(session_data)
return redirect('/dashboard')
else:
print(PetFormData.form_submitted)
return HttpResponse('Form already submitted', content_type="text/plain")
else:
form = PetForm()
return render(request, 'petform.html', {'form': form})
Successfully submitting the form once presumably stores something in the database. A subsequent visit to that page can interrogate the database, discover it has already been done, and display the appropriate next page.
Something like this (I don't fully understand your problem)
if PetFormData.objects.filter( auth_user_email = request.user).exists() :
return redirect('/dashboard')
# OK, user hasn't submitted yet.

Modifying the django-invitations Package to allow Team Functionality

Using django-invitations, a user can invite multiple emails at once to their team using the code below that I obtained from this question: How to associate invited users with the inviter's Company/group?
#login_required
def invite_multi_premium(request):
# Get users that are in the company's user database as well as users that have been invited
taskly_users = TasklyUser.objects.filter(team=request.user.team)
Invitations = get_invitation_model()
# I'm afraid this is going to get all invited users, not just those that belong to the company
invited_users = Invitations.objects.filter()
if request.method == 'POST':
form = InviteMultipleEmailsForm(request.POST)
print(request.POST, "THIS IS THE REQUEST POST")
invitees2 = request.POST['emails']
invitees = invitees2.split(', ')
for invitee in invitees:
Invitation = get_invitation_model()
try:
invite = Invitation.create(invitee, inviter=request.user, team=str(request.user.team))
invite.send_invitation(request)
except IntegrityError as e:
print(type(e))
print(dir(e))
return render(request, 'invitations/forms/_invite_multi.html', {
"form":form
})
else:
form = InviteMultipleEmailsForm()
return render(request, 'invitations/forms/_invite_multi.html', {
"form":form
})
When the user follows the invitation link, they are brought to a signup form (django allauth) with their email address pre-filled. When they sign up, they are not associated with the team that the Inviter is on. Here is the part of the package I modified to get "team" added to the Invitations model, but I can't get the "team" to pass from the Invitations model to the User model.
class Invitation(AbstractBaseInvitation):
email = models.EmailField(unique=True, verbose_name=_('e-mail address'),
max_length=app_settings.EMAIL_MAX_LENGTH)
created = models.DateTimeField(verbose_name=_('created'),
default=timezone.now)
team = models.CharField(max_length=255, null=True, blank=True)
#classmethod
def create(cls, email, inviter=None, team=None, **kwargs):
key = get_random_string(64).lower()
instance = cls._default_manager.create(
email=email,
key=key,
inviter=inviter,
team=team,
**kwargs)
return instance
def key_expired(self):
expiration_date = (
self.sent + datetime.timedelta(
days=app_settings.INVITATION_EXPIRY))
return expiration_date <= timezone.now()
def send_invitation(self, request, **kwargs):
current_site = kwargs.pop('site', Site.objects.get_current())
invite_url = reverse('invitations:accept-invite',
args=[self.key])
invite_url = request.build_absolute_uri(invite_url)
ctx = kwargs
ctx.update({
'invite_url': invite_url,
'site_name': current_site.name,
'email': self.email,
'team': self.team,
'key': self.key,
'inviter': self.inviter,
})
email_template = 'invitations/email/email_invite'
get_invitations_adapter().send_mail(
email_template,
self.email,
ctx)
self.sent = timezone.now()
self.save()
signals.invite_url_sent.send(
sender=self.__class__,
instance=self,
invite_url_sent=invite_url,
inviter=self.inviter)
def __str__(self):
return "Invite: {0}".format(self.email)
Solved it by moving the receiving signal into my app/models.py instead of in the app/signals.py. I read in this post: https://simpleisbetterthancomplex.com/tutorial/2016/07/28/how-to-create-django-signals.html that you should avoid putting the receiver in the models.py file but the explanation is lacking in detail.

How do I compare form inputs with database values in Django?

I have a form to input a user id and I want compare this id with database values (usrId).
forms.py
from django import forms
from .models import UserInfo
class NameForm(forms.Form):
your_id = forms.CharField(label='Your id', max_length=100)
def clean(self):
cleaned_data = super(NameForm, self).clean()
your_id = cleaned_data.get("your_id")
p = UserInfo.objects.all()
if your_id:
for i in p:
if i.usrId not in your_id:
raise forms.ValidationError(
"User not exist."
)
When I do this nothing happens and I get User not exist. for any value.
models.py
class UserInfo(models.Model):
name = models.CharField(max_length=200)
usrId = models.CharField(max_length=200)
age = models.CharField(max_length=200)
poste = models.CharField(max_length=200)
date1 = models.DateTimeField('date of recruitment')
def __str__(self): # __unicode__ on Python 2
return self.name
views.py
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return generate_pdf(request, Type_id)
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'rh/detail.html', {'form': form, 'type_id': Type_id})
Assuming that the user id that you are trying to match does indeed exists (log that id and query the database manually to make sure). Your code should be changed as follows:
try:
p = UserInfo.objects.get(id=your_id)
except UserInfo.DoesNotExist:
raise forms.ValidationError("User not exist.")
This code is shorter and more efficient (you are not fetching all the user objects as in the current version)

Dynamic form generation

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().

Categories