Django Foreign Key Relationship And Creation - django

I have the following:
models.py
class User(models.Model):
email = models.EmailField(unique=True)
passwd = models.CharField()
sessionID = models.CharField()
class StudentProfile(models.Model):
school = models.CharField()
name = models.CharField()
user = models.ForeignKey(User, unique=True)
I don't understand how to create Users and StudentProfiles in django and have them linked. Could somebody please provide some sample code for creating a User, then creating a StudentProfile that refers to the User. Could they also post sample access code, ie let's say I have an email, please show how I would lookup the User, and then access the accompanying Profile information. Could I write a method as part of the User model to do this?
inb4 Why aren't you using the Django User model?
This is as much of a thought exercise as anything, I also want to roll my own because the Django stuff was much more than I needed. I prefer it sweet and simple.
Thanks!

As addition to zhyfer's answer – You can approach things even a bit more basic:
user = User()
user.email = 'anon#anon.com'
user.save()
profile = StudentProfile()
profile.school = 'MIT'
profile.user = user
profile.save()
To get the related items you can also use the related manager:
user.profile_set.all()[0] should return the related StudentProfile for the created user.
And therefore user.profile_set.all()[0].school should return MIT while
profile.user.email returns anon#anon.com
This all is a bit more elaborated in this blog post: Doing things with Django models
And finally you could write the get_profile method of the user-model like this:
def get_profile(self):
return self.profile_set.all()[0]

If you have the above models,you can access a profile given an email via:
profile = StudentProfile.objects.get(user__email = 'test#test.com')
school = profile.school
I think that if you want a method in the user model to do this you can basically do the same thing:
def get_profile(email):
return StudentProfile.objects.get(user__email = 'test#test.com')
To Link:
u = User.objects.create(email="test#test.com", passwd="test", sessionId=1)
StudentProfile.objects.create(school="test", name="student", user=u)

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)

FactoryBoy / Django - OneToOneField duplicate key error

I am writing tests for a large Django application with multiple apps. As part of this process I am gradually creating factories for all models of the different apps within the Django project.
However, I've run into some confusing behavior with FactoryBoy
Our app uses Profiles which are linked to the default auth.models.User model with a OneToOneField
class Profile(models.Model):
user = models.OneToOneField(User)
birth_date = models.DateField(
verbose_name=_("Date of Birth"), null=True, blank=True)
( ... )
I created the following factories for both models:
#factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
user = factory.SubFactory('yuza.factories.UserFactory')
birth_date = factory.Faker('date_of_birth')
street = factory.Faker('street_name')
house_number = factory.Faker('building_number')
city = factory.Faker('city')
country = factory.Faker('country')
avatar_file = factory.django.ImageField(color='blue')
tenant = factory.SubFactory(TenantFactory)
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
username = factory.Faker('user_name')
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.Faker('email')
is_staff = False
is_superuser = False
is_active = True
last_login = factory.LazyFunction(timezone.now)
profile = factory.RelatedFactory(ProfileFactory, 'user')
Which I then run the followings tests for:
class TestUser(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
user = UserFactory()
self.assertTrue(user)
self.assertTrue(user.profile)
self.assertTrue(user.profile.tenant)
class TestProfile(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
profile = ProfileFactory()
self.assertTrue(profile)
All tests in TestUser pass, but the TestProfile fails on the factory initialization ( profile = ProfileFactory()) and raises the following error:
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
Its not clear to me why a duplicate User would already exist, (there should only be one call to create one right?, especially since any interfering signals have been disabled)
My code was based on the example from the FactoryBoy documentation, which also dealt with Users / Profiles that are connected via a OneToOneKey
Does anyone know what I am doing wrong?
Update
As per the suggestions of both Bruno and ivissani I've changed the user line in the ProfileFactory to
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
Now all the tests described above pass successfully!
However I still run into the following issue - when other factories call the UserFactory the
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
still returns.
I've included an example of a factory calling the UserFactory below, buts its happening to every factory that has a user field.
class InvoiceFactory(factory.django.DjangoModelFactory):
class Meta:
model = Invoice
user = factory.SubFactory(UserFactory)
invoice_id = None
title = factory.Faker('catch_phrase')
price_paid = factory.LazyFunction(lambda: Decimal(0))
tax_rate = factory.LazyFunction(lambda: Decimal(1.21))
invoice_datetime = factory.LazyFunction(timezone.now)
Changing the user field on the InvoiceFactory to
user = factory.SubFactory(UserFactory, profile=None)
Helps it pass some of the tests, but eventually runs into trouble since it no longer has a profile associated with it.
Weirdly the following (declaring the user before the factory) DOES work:
self.user = UserFactory()
invoice_factory = InvoiceFactory(user=self.user)
Its not clear to me why I still keep running into the IntegrityError here, calling the UserFactory() now works fine.
I think it's because your ProfileFactory creates a User instance, using the UserFactory which itself tries to create a new Profile instance using the ProfileFactory.
You need to break this cycle, as described in the documentation you link to:
# We pass in profile=None to prevent UserFactory from
# creating another profile (this disables the RelatedFactory)
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
If this doesn't work for you and you need more advanced handling, then I suggest implementing a post_generation hook where you can do more advanced things.
EDIT:
Another option is to tell Factory Boy to not recreate a Profile if there is already one for the User by using the django_get_or_create option:
#factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
django_get_or_create = ('user',)
If you do so, you might be able to remove the profile=None that I suggested before.
EDIT 2:
This might also help, change the UserFactory.profile using a post_generation hook:
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
...
# Change profile to a post_generation hook
#factory.post_generation
def profile(self, create, extracted):
if not create:
return
if extracted is None:
ProfileFactory(user=self)
EDIT 3
I've just realised that the username field in your UserFactory is different from the one in factroy boy's documentation, and it's unique in Django. I wonder if this doesn't cause some old instances to be reused because the username is the same.
You may want to try changing this field to a sequence in your factory:
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
# Change to sequence to avoid duplicates
username = factory.Sequence(lambda n: "user_%d" % n)

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.

Django Filter ForeignKey relationships in get_queryset

I wonder what to do here.
So I have an EmailAddress class which has a ForeignKey relationship to my User class
class EmailAddress(models.Model):
user = models.ForeignKey(allauth_app_settings.USER_MODEL, verbose_name=_('user'))
email = models.EmailField(unique=app_settings.UNIQUE_EMAIL,
verbose_name=_('e-mail address'))
I also have a UserProfile class which has a OneToOneField relationship with the mentioned User like so:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
attribute= models.CharField("attr", max_length=150, blank=False)
Now in admin.py I want to filter the Users so that the current user who is logged in to admintool can only see emails from users who share some attribute. The catch is that the attribute is saved in UserProfile and I need to get there through the email.
Basically I don't know how to access the 'attribute' which is in the UserProfile. I understand that I want to filter users by taking the email finding the user that this email belongs to and then compare his 'attribute' to the user who send the request to decide whether to show it to him or not.
def get_queryset(self, request):
user = [User whos email is in the list]
qs = [the queryset]
[and then]
return qs.filter([filter so the user attribute = the request.user attribute])
Im using allauth btw. in case you wonder what
allauth_app_settings.USER_Model
stands for.
Have a nice day!
You need to use the double-underscore syntax to follow the relationships.
qs.filter(user__userprofile__attribute=request.user.attribute)

How to get logged-in user info in Django's forms.py

I have created a Profile model including the Gender info. There is also models called Dorm and Registration (not used for user registration) like this:
class Registration(models.Model):
user = models.ForeignKey(User)
pref1 = models.ForeignKey(Dorm, related_name="pref1",verbose_name=u"Preference 1",null=True)
...
friend1 = models.CharField(u"Friend 1", max_length=15,blank=True)
class Dorm(models.Model):
name = models.ForeignKey(Building)
gender = models.CharField(u"Gender", max_length=1, blank=True, choices=GENDER_CHOICES)
Now, i am trying to generate a form for this Registration model with forms.ModelForm like this:
class RegistrationForm(forms.ModelForm):
dorms = Dorm.objects.filter(gender='M')
pref1 = forms.ModelChoiceField(queryset=dorms, empty_label=None)
...
class Meta:
model = Registration
exclude = ('user')
as you can see in the second line, i am querying the dorms with a hardcoded gender value M. Instead of the hardcoded value, I need to get the users' gender, and query the database with that gender information.
I have been searching the documentation but I could not find anything. Can you help me? How can I learn the logged-in User' profile information in Django Forms?
So without using some sort of monkeying around of the init function a "form_factory" is probably the best approach.
def RegFormFactory(user)
dorms = Form.objects.filter(gender = "user.gender")
class _RegistrationForm(forms.ModelForm):
pref1 = forms.ModelChoiceField(queryset = dorms, empty_label=None)
class Meta:
model = Registration
exclude = ('user')
return _RegistrationForm
then use:
formclass = RegFormFactory(user)
form_inst = formclass()
...
form_inst = formclass(request.POST)
This is described very nicely in a blog post here: So you want a dynamic form.
James Bennett wrote a blog post that should explain perfectly what you need: So you want a dynamic form