using session to store data from anonymous user - django

in my django app, I have a user wishlist feature. I want that an anonymous user can add a product to his wishlist.
models.py
class User_Wishlist(models.Model):
created_by = models.ForeignKey(User, default=None, null =True, blank=True)
products = models.ManyToManyField(Products, null=True)
# some Products model
views.py
# to add an item from the user wishlist
def add_userwl(request, id):
products = Products.objects.filter(id__iexact=id)
product = products[0]
if request.user.is_authenticated():
userwlt = get_sl(request, request.user) #gets the auth user wishlist
userwlt.products.add(product)
sl_products = userwlt.products.all()
else:
if "wl_user" in request.session:
wl_user = request.session["wl_user"]
else:
wl_user = User_Wishlist(created_by = request.user) #here lies the problem!
request.session["wl_user"] = wl_user
wl_user.products.add(product)
wl_products = wl_user.products.all()
return render_to_response('getsl.html', {
'wl_products':wl_products
}, context_instance=RequestContext(request))
Error: Cannot assign "": "User_Wishlist.created_by" must be a "User" instance.
So, it can't create a wishlist for an anon user. Also if I just do wl_user = User_Wishlist(), it can't access the manytomany field 'products' without assigning a user id.
Any help on this would be great. Thanks!
p.s: I also want to save this user wishlist once the anon user signs up, but thats a later problem

Your concept of an anonymous user seems wrong. If you wish to bound data to a certain 'user' you will have to create an 'user' for them. Otherwise whenever the session expires or the 'user' resets his or her browser's cache the data is gone.
Now how an 'user' is defined is entirely up to you. It could be a 'cart' or a 'customer' or anything else. There's no reason you can't use a different abstraction then the one provided by Django's auth module.
Most sites that implement this properly allow your not-so-much 'anonymous' session to be bound to an optional account at some point (which might as well be an oauth or openid solution).

The much simpler solution is to require that a wishlis is tied to either a user object or a session object. So allow for those fields to be null and implement logic that favours the user object.

Related

Pulling data associated with user account in Django

I'm creating a model in Django with the following syntax. The model has a foreign key of registered users. However, I want to serialize this model that will return a Json file only associated with the logged in user. Can you give your recommendations? Or is there an alternative way to extract the information from the model using different approach?
class Education(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
school = models.CharField(max_length=50)
year = models.CharField(max_length=10)
Sample photo
For instance, it will only show the first field associated with the account
To filter objects related to a user, simply get hold of the user and apply filter based on the user.
user_education_queryset = Education.objects.filter(user=user)
if you are doing this in the view where user objects is available in request context, you could simply do user=request.user Note: the user has to be logged in, else this will return anonymouse user object which will fail to query the database.
Summary:
Get the user object and filter by the user field in education model using the user object obtained.
mike = User.objects.get(id=1) # or mike = User.objects.get(username='mike')
then
mike_education_query = Education.objects.filter(user=mike)
Because of the suggestions, I was able to query the results per user with this code:
class EducationViewSet(viewsets.ModelViewSet):
serializer_class = EducationSerializer
def get_queryset(self):
user = self.request.user
account = User.objects.get(id=user.id)
return Education.objects.filter(user=account)

How enforce filling up the user profile after first social login in django-allauth?

I've created UserProfile model in my application:
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True,
verbose_name=_('user'),
related_name='user_profile')
locality = models.CharField(max_length=85)
voivodship = models.CharField(max_length=20,
choices=Vovoidship.choices,
validators=[Vovoidship.validator])
postcode = models.IntegerField()
street = models.CharField(max_length=75)
building = models.SmallIntegerField()
premises = models.CharField(max_length=80)
phonenumber = PhoneNumberField(blank=True)
#staticmethod
def post_save_create(sender, instance, created, **kwargs):
if created:
profile, created = UserProfile.objects.get_or_create(user=instance)
post_save.connect(UserProfile.post_save_create, sender=User)
Now, I felt into my own trap. I don't want to loose constraints and keep the requirement in the database that address fields need to be filled up. I'm using django-allauth. While using the setting ACCOUNT_SIGNUP_FORM_CLASS = 'management.forms.SignupForm' solves the problem for traditional sign up form, if the user logs in first time using the social account I got hit by constraint violation for obvious reasons:
IntegrityError at /accounts/google/login/callback/
null value in column "postcode" violates not-null constraint
DETAIL: Failing row contains (4, , , null, , null, , ).
Hence the question, how to correctly implement the request for filling up the information for fields in the application UserProfile? I'm surprised that django-allauth doesn't have a build in handler for that the same way as ACCOUNT_SIGNUP_FORM_CLASS is done.
As I'm new to Django please assume I rather don't know something than it should be obvious. Thanks.
I think you need to:
1.- Create your custom Signup Class, for you to do the additional work
class SignupForm(forms.Form):
locality = forms.CharField(max_length=85)
voivodship = forms.CharField(max_length=20)
postcode = forms.IntegerField()
etc.
def signup(self, request, user):
# I think the profile will exist at this point based on
# your post_save_create. But if not, just create it here
if user.user_profile:
user.user_profile.locality = self.cleaned_data['locality']
user.user_profile.voivodship = self.cleaned_data['voivodship']
user.user_profile.postcode = self.cleaned_data['postcode']
...
user.user_profile.save()
2.- Set ACCOUNT_SIGNUP_FORM_CLASS = 'yourproject.yourapp.forms.SignupForm' to have allauth use your form
3.- Set SOCIALACCOUNT_AUTO_SIGNUP=False to ensure the form is presented even with social signup.
With some credits to davka I've managed to form a working solution which required creating UserProfile object inside signup() method of the SignupForm class, but because of database/model constrains it has be be filled with data during creation. The result:
class SignupForm(ModelForm):
first_name = CharField()
last_name = CharField()
class Meta:
model = UserProfile
exclude = ['user', 'phonenumber']
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
profile, created = UserProfile.objects.get_or_create(
user=user, defaults={
'locality': self.cleaned_data['locality'],
'voivodship': self.cleaned_data['voivodship'],
'postcode': self.cleaned_data['postcode'],
'street': self.cleaned_data['street'],
'building': self.cleaned_data['building'],
'premises': self.cleaned_data['premises'],
})
if created: # This prevents saving if profile already exist
profile.save()
The solution doesn't totally fit into DRY principle, but shows the idea. Going further it could probably iterate over results matching model fields.
Two elements need to be set correctly in settings.py:
ACCOUNT_SIGNUP_FORM_CLASS = 'yourapp.forms.SignupForm' to enable this form in allauth
SOCIALACCOUNT_AUTO_SIGNUP = False this - contrary to the intuition - let the allauth display the form before finishing the signup if the user selected social sign in but don't have an account; it works safely if the account already exists (username and/or e-mail address depending on other settings) as just does't allow to finish registration (why they call it sign up?) and the user is forced to log in and link social account.

How do I link these two models such that one will update that same instance?

I really want to build this app with Django that lets people register and create User instances that can be edited. Each User instance is already linked to a UserProfile with OneToOne because I didn't want to mess with the original User model. The UserProfile will have a field where he/she can register a game if that person is logged in.
ie. Billy wants to register for Monday Smash Melee. He logs in, clicks an option on a form, the UserProfile linked to User, Billy, will update the registered game choice and user tag to the user profile.
The part with the user profile linking to the user works fine, but I don't know how to update the UserProfile with the new tournament registration form so that it can change the UserProfile fields that's linked to the user that is logged in.
Django Models:
class UserProfile(models.Model):
#User profile for registered users. SEPARATE USERBASE TO PLAYER_RANKING
#To Do: add more customizeability and more access for registered.
#weekly e-mails, ability to register for weeklies...
user = models.OneToOneField(User)
picture = models.ImageField(upload_to='profile_images', blank=True)
MON = 'ME'
TUE = 'S4'
THR = 'PM'
reg_game_choices = (
(MON, "Melee"),
(TUE, "Smash 4"),
(THR, "PM"),
)
reg_game_choice = models.CharField(max_length=2,
choices=reg_game_choices,
default="")
user_tag = models.CharField(max_length=60, default = "")
def __str__(self):
return self.user.username
Forms:
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput())
class Meta:
model = User
fields = ('username', 'password')
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('picture',)
class TournyRegForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('reg_game_choice', 'user_tag',)
View:
#login_required
def tourny_reg(request):
#Registering for tournaments
context_dict = {}
weekday = datetime.datetime.today().weekday()
day_names = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY']
game_days = ['SMASH MELEE', 'SMASH 4', 'CLOSED', 'PROJECT M & FIGHTING GAMES',
'FRIENDLIES', 'CLOSED', 'CLOSED']
day_title = day_names[weekday]
game_day = game_days[weekday]
context_dict['day'] = day_title
context_dict['game_of_the_day'] = game_day
if request.method == 'POST':
tourny_form = TournyRegForm(data=request.POST)
if tourny_form.is_valid():
tourny_form.save()
else:
print (tourny_form.errors)
else:
tourny_form = TournyRegForm()
context_dict['tourny_form'] = tourny_form
return render(request, 'Kappa/tourny_reg.html', context_dict)
It shows up perfectly fine in html and on the local server, but when I try, it gives me an integrity error.
IntegrityError at /Kappa/tourny_reg/
NOT NULL constraint failed: Kappa_userprofile.user_id
Exception Value:
NOT NULL constraint failed: Kappa_userprofile.user_id
▶ Local vars
C:\Users\Kyle\Documents\GitHub\Kappa_Ranks\Kappa\views.py in tourny_reg
So basically, you want to know how to save an instance of something which is related to the logged-in user. That's easy.
To explain why you are getting a NOT NULL error: Your TournyRegForm class has not been told to display an input field for 'user', so it isn't. So when you go to save the form, None is being filled in for the user field. The database is complaining because a 'NOT NULL' field has a NULL value, which is a problem.. so this error is legitimate.
But it's ok that this field is not on the form.. because you don't want the user telling us who they are via the form, you want to get the information about who they are by the fact that they are logged in. The Django auth module puts this information in the Request object where you can easily get at it. All you need to do is to fill in the correct user before the model is saved, like this:
if tourny_form.is_valid():
# commit= False tells the modelform to just create the model instance
# but don't save it yet.
user_profile = tourny_form.save(commit=False)
# associate this user_profile with the logged in user.. it is always
# present in the request object if you are using django's auth module.
user_profile.user = request.user
# now save it
user_profile.save()
So that takes care of saving a model that is related to the currently logged in user. But you have other problems. For example, do you want to save a new UserProfile each time? I don't think you do.. So on your GET you need to do something like this:
user_profile = UserProfile.objects.filter(user=request.user).first()
tourny_form = TournyRegForm(instance=user_profile)
This will fetch the UserProfile of the currently logged=in user from the database, then initialize the form with that instance, so when the user comes back they will be able to edit their profile.
Now, if you actually want the user to be able to register for multiple games.. you will need a Game model for storing the game information, with one-to-many relationship with your UserProfile. This works by having a ForeignKey field in the Game model which relates it to UserProfile.. so each user will have only one UserProfile but could have multiple Games.

Filter M2M in template?

In my model, I have the following M2M field
class FamilyMember(AbstractUser):
...
email_list = models.ManyToManyField('EmailList', verbose_name="Email Lists", blank=True, null=True)
...
The EmailList table looks like this:
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
In the app, the user should only see records that is_active=True and is_managed_by_user=True.
In the Admin side, the admin should be able to add a user to any/all of these groups, regardless of the is_active and is_managed_by_user flag.
What happens is that the Admin assigns a user to all of the email list records. Then, the user logs in and can only see a subset of the list (is_active=True and is_managed_by_user=True). This is expected behavior. However, what comes next is not.
The user deselects an email list item and then saves the record. Since M2M_Save first clears all of the m2m records before it calls save() I lose all of the records that the Admin assigned to this user.
How can I keep those? I've tried creating multiple lists and then merging them before the save, I've tried passing the entire list to the template and then hiding the ones where is_managed_by_user=False, and I just can't get anything to work.
What makes this even more tricky for me is that this is all wrapped up in a formset.
How would you go about coding this? What is the right way to do it? Do I filter out the records that the user shouldn't see in my view? If so, how do I merge those missing records before I save any changes that the user makes?
You might want to try setting up a model manager in your models.py to take care of the filtering. You can then call the filter in your views.py like so:
models.py:
class EmailListQuerySet(models.query.QuerySet):
def active(self):
return self.filter(is_active=True)
def managed_by_user(self):
return self.filter(is_managed_by_user=True)
class EmailListManager(models.Manager):
def get_queryset(self):
return EmailListQuerySet(self.model, using=self._db)
def get_active(self):
return self.get_queryset().active()
def get_all(self):
return self.get_queryset().active().managed_by_user()
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
objects = EmailListManager()
views.py:
def view(request):
email = EmailList.objects.get_all()
return render(request, 'template.html', {'email': email})
Obviously there is outstanding data incorporated in my example, and you are more than welcome to change the variables/filters according to your needs. However, I hope the above can give you an idea of the possibilities you can try.
In your views you could do email = EmailList.objects.all().is_active().is_managed_by_user(), but the loading time will be longer if you have a lot of objects in your database. The model manager is preferred to save memory. Additionally, it is not reliant on what the user does, so both the admin and user interface have to talk to the model directly (keeping them in sync).
Note: The example above is typed directly into this answer and has not been validated in a text editor. I apologize if there are some syntax or typo errors.

How can I force the user id as value when working with django rest framework?

I want my rest api to be able to add objects, but there is a twist.
My model has a field that's called user which can either be None for anonymous users or the id of whoever sets it.
How can I enforce this in an elegant way?
Also if the user field is None I want the id of the new object to be stored in an array in the anonymous users session so it can be assigned if he ever decides to register.
Here is how I created the viewset:
class PointAPIView(viewsets.ModelViewSet):
queryset = Point.objects.all()
serializer_class = PointSerializer
permission_classes = (IsOwnerOrReadOnly,)
I suspect that I have to write a custom create method is that right? If so how does it need to look like? The documentation only says that it's there, but not how it needs to be structured and what it needs to return. Can you show me an example of a dummy method where I can put my logic?
Thank you for your time!
EDIT: Because someone asked. The Model would look something like this:
class Point(models.Model):
user = models.ForeignKey('auth.User')
value = models.IntegerField()
Now the question is how I can force the user field to be request.user when creating a point object via the rest api.
In your PointSerializer override create method and get request object to extract current user and assign it to created point.
class PointSerializer(serializers.ModelSerializer):
//point fields
class Meta:
model = Point
fields = ('value','user')
def create(self,validated_data):
request = request = self.context['request']
user = request.user
point = Point.objects.create(value=validated_data['value'])
point.user = user
point.save()
return point
not sure if you can nodify validated_data, if not you'd need to copy it over
def create(self, validated_data):
request = self.context['request']
user = request.user
if user.is_authenticated():
validated_data.update({
'user': user.id
})
point = Point.objects.create(**validated_data)
return point