On my current project I want the user to be able to fill in forms without having to sign up first (to make them more likely to use the service).
On the below view I'm trying to either save the registered user with the form data, or if the user isn't registered save the Session ID as a temporary user id.
However when I try to use the session ID it returns none. I'm not sure why the data is missing? (Session have the default django setup in apps and middleware as per the docs). Note when a user is logged in it seem to have a user id but not when no user is logged in.
View:
class ServiceTypeView(CreateView):
form_class = ServiceTypeForm
template_name = "standard_form.html"
success_url = '/'
def form_valid(self, form):
if self.request.user.is_authenticated():
form.instance.user = self.request.user
else:
form.instance.temp_user = self.request.session.session_key
super().form_valid(form)
online_account = form.cleaned_data['online_account']
if online_account:
return redirect('../online')
else:
return redirect('../address')
Model:
class EUser(models.Model):
supplier1 = models.OneToOneField(SupplierAccount)
supplier2 = models.OneToOneField(SupplierAccount)
supplier3 = models.OneToOneField(SupplierAccount)
online_account = models.BooleanField()
address = models.OneToOneField(Address, null=True)
temp_user = models.CharField(max_length=255, null=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True, default=None)
class SupplierAccount(models.Model):
supplier = models.ForeignKey(Supplier)
username = models.CharField(max_length=255)
password = models.CharField(max_length=255)
Form:
class ServiceTypeForm(forms.ModelForm):
# BOOL_CHOICES = ((False, 'No'), (True, 'Yes'))
# online_account = forms.BooleanField(widget=forms.RadioSelect(choices=BOOL_CHOICES))
def __init__(self, *args, **kwargs):
super(ServiceTypeForm, self).__init__(*args, **kwargs)
self.fields['service_type'].initial = 'D'
class Meta:
model = EUser
fields = ('service_type', 'online_account')
The session key will exist if there is data set in the session dictionary already. Logged in users have a session key because Django stores authentication related data in the session by default, so a key will always be assigned because of that.
You can ensure that a key always exists by tossing some data into the session storage before trying to get the key.
Related
I made a single function for register and login with mobile and otp. The register part is in the else part of the function, and the if part is the login function. Every time I log in with the already registered number, it makes a new object in the database, and I don't want that. I want to just update the otp part from when the number was registered in the database.
views.py
class RegistrationAPIView(APIView):
permission_classes = (AllowAny,)
serializer_class = ProfileSerializer
def post(self, request):
mobile = request.data['mobile']
data = Profile.objects.filter(mobile = mobile).first()
if data:
serializer = self.serializer_class(data=request.data)
mobile = request.data['mobile']
if serializer.is_valid(raise_exception=True):
instance = serializer.save()
content = {'mobile': instance.mobile, 'otp': instance.otp}
mobile = instance.mobile
otp = instance.otp
print("Success")
send_otp(mobile,otp)
return Response(content, status=status.HTTP_201_CREATED)
else:
return Response({"Error": "Login in Failed"}, status=status.HTTP_400_BAD_REQUEST)
else:
serializer = self.serializer_class(data=request.data)
mobile = request.data['mobile']
if serializer.is_valid(raise_exception=True):
instance = serializer.save()
content = {'mobile': instance.mobile, 'otp': instance.otp}
mobile = instance.mobile
otp = instance.otp
send_otp(mobile,otp)
return Response(content, status=status.HTTP_201_CREATED)
else:
return Response({"Error": "Sign Up Failed"}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['mobile']
def create(self, validated_data):
instance = self.Meta.model(**validated_data)
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
instance.otp = str(random.randint(1000 , 9999))
instance.save()
return instance
models.py
'''
class Profile(models.Model):
mobile = models.CharField(max_length=20)
otp = models.CharField(max_length=6)
'''
I'm assuming here that you're using DRF Serializers. If that's the case, note from the documentation that :
Calling .save() will either create a new instance, or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class:
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
As an aside, you probably want to do a get_or_create or create_or_update with defaults instead of having the if/else clause.
Option A: First thing first. If there is only and only one mobile number for each Profile you should add a unique constraint for your Profile model as well. Something like:
class Profile(models.Model):
mobile = models.CharField(max_length=20, unique=True)
otp = models.CharField(max_length=6)
Do not forget to make migration and migrate these new changes to your database (also note that there might be some duplicate records in Profile table so if you're not on production server first delete all of your records and then make this migration).
And then make this change to your serializer's create method:
def create(self, validated_data):
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
instance = self.Meta.model.objects.update_or_create(**validated_data, defualts=dict(otp=str(random.randint(1000 , 9999))))[0]
return instance
with update_or_create now you're sure that if the record with specific mobile exists you will update that and if not you will create new one.
Option B: But if you don't want to make this change to your database for any reason you can just simply do this:
def create(self, validated_data):
global totp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret, interval=300)
otp = totp.now()
if self.Meta.model.objects.filter(**validated_data).exists():
instance = self.Meta.model.objects.filter(**validated_data).last()
instance.otp = str(random.randint(1000 , 9999))
instance.save()
else:
instance = self.Meta.model(**validated_data)
instance.otp = str(random.randint(1000 , 9999))
instance.save()
return instance
Note that there might be multiple records in your table with same mobile number as long as there isn't any constraint on that model and here we are only updating latest record in your Profile table. I hope these two options solve your problem.
I've got a ModelForm with a number of attributes - in the CreateView form_valid method I'm trying to save a users form inputs as session data (which I check for in the get_initial method if they visit the form again)
ModelForm:
class OrgForm(forms.ModelForm):
"""Form definition for a Org."""
class Meta:
"""Meta definition for OrgForm."""
model = Org
fields = (
"full_name",
"short_name",
"feature",
"state",
"email",
)
View:
class OrgCreateView(CreateView):
"CreateView for OrgForm"
model = Org
form_class = OrgForm
success_url = "/home/"
def form_valid(self, form):
response = super().form_valid(form)
# Set the form data to session variables
responses = {}
for k, v in form.cleaned_data.items():
responses[k] = v
self.request.session["org_form_data"] = responses
return super().form_valid(form)
def get_initial(self):
initial = super(OrgCreateView, self).get_initial()
# Check for any existing session data
# This is present if they had filled this out
# and then came back again later to fill out again
if "org_form_data" in self.request.session:
# They have data - loop through and fill it out
for key, value in self.request.session["org_form_data"]:
initial[key] = value
return initial
Model:
class Org(models.Model):
"""Model definition for Org."""
full_name = models.CharField(max_length=150)
short_name = models.CharField(max_length=25, blank=True, null=True)
state = models.CharField(max_length=50)
is_active = models.BooleanField(default=False)
feature = models.ForeignKey(Feature, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now=False, auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True, auto_now_add=False)
email = models.EmailField()
I have an attribute in there that has a Foreign Key - so when I save the form I get the error:
Object of type Feature is not JSON serializable
I'm not sure how best to go about getting around that error - or even if this is the right way to go about it.
Any help is appreciated!
You can use sessions in Django:
Session Configuration
To set up a session in Django, You need to add two things in your settings.py:
‘django.contrib.sessions.middleware.SessionMiddleware 'to MIDDLEWARE
'django.contrib.sessions 'to INSTALLED_APPS.
Run python manage.py migrate to populate the table.
The table has three columns:
session_key
session_data
expire_date
Reading and Writing Session Data
A Django request object has a session attribute that acts like a dictionary.
Set session data
request.session['user_id'] = ‘20’
request.session['team'] = ‘Barcelona’
like request.session['your cookie name'] = your value
Read session data
request.session.get('user_id') # returns ‘20’
request.session.get('team') # returns ‘Barcelona’
Delete session data
del request.session['user_id']
del request.session['user_id']
I've tried this method, and it works perfectly!
You can ask Django for session values:
if request.session('cookiename') == True:
print("It is TRUE!")
Ok, so what I'm trying to do is allow the user to add a "product" to their shop but without having to choose the shop to add it to as each user will only have ONE shop.
I'm getting the:
"IntegrityError at /shop/product/add/
NOT NULL constraint failed: shop_product.business_id"
This is what's being shown in the local variables:
Local Vars
Local Vars:
Variable Value
__class__ <class 'shop.views.ProductCreate'>
form <AddProductForm bound=True, valid=True, fields=(product_name;product_desc;product_image)>
s <Shop: 4>
self <shop.views.ProductCreate object at 0x048B0370>
user 10
Now I believe the issue might be the "s" variable's as the code is actually getting the correct shop.. but it's also adding that weird "
My Code as it is right now.
models.py
# Shop Model. A Shop Object will be created when the user registers
class Shop(models.Model):
name = models.CharField(max_length=150)
owner = models.OneToOneField(User, related_name="owner")
shop_logo = models.FileField()
def __str__(self):
return str(self.name) + ": " + str(self.owner)
def create_shop(sender, **kwargs):
user = kwargs["instance"]
if kwargs["created"]:
up = Shop(owner=user)
up.save()
post_save.connect(create_shop, sender=User)
def shoplogo_or_default(self, default_path='/static/images/dft/no-img.png'):
if self.shop_logo:
return self.shop_logo
return default_path
# The class that will link a product to the shop
class Product(models.Model):
product_name = models.CharField(max_length=250)
# connect the product to the shop
business = models.ForeignKey(Shop, on_delete=models.CASCADE, related_name="products")
product_desc = models.TextField()
product_image = models.FileField()
def __str__(self):
return self.product_name
views.py
class ProductCreate(CreateView):
model = Product
form_class = AddProductForm
template_name = 'shop/add-product.html'
def form_valid(self, form):
form.save(commit=False)
# get current logged in user
user = self.request.user.id
# match the current logged in user to an owner in the Shop model
s = Shop.objects.get(owner=user)
# get the id of that owner's shop identification number
form.business = str(s.id)
form.save()
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
return super(ProductCreate, self).form_valid(form)
The above should in theory get the current logged in user, match that user to a shop within the shop model as an owner and then get that shop ID.
forms.py
class AddProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['product_name', 'product_desc', 'product_image']
exclude = ['business']
I'm rather new to Django and a student so I'd like to apologise if you see anything weird.
Thank you :)
You're close, but don't try to edit the shop value into the form. Instead, capture the in-memory Product instance from saving the form and assign its business attribute:
def form_valid(self, form):
new_product = form.save(commit=False)
# get current logged in user
user = self.request.user.id
# match the current logged in user to an owner in the Shop model
s = Shop.objects.get(owner=user)
# assign the shop instance to the product
new_product.business = s
# record the product to the database
new_product.save()
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
return super(ProductCreate, self).form_valid(form)
We're required to have two separate forms for two different types of users. Call them Client and Provider. Client would be the parent, base user, while Provider is a sort of extension. At any point a Client could become a Provider as well, while still maintaining status and information as a Client. So a Provider has both permissions as a Client and as a Provider.
I'm new to Django. All we're trying to do is register either user type, but have a one to one relation between Provider and Client tables if a user registers as a Provider straight away.
The issue we're having is in the adapter, we think. A provider registers fine, but ends up in the users_user table with no entry in the generated users_provider table. Is it the way we're trying to save and relate these two entities in the database, or something else?
We're trying to utilize allauth for authentication and registration.
Our code:
models.py:
class User(AbstractUser):
name = models.CharField(_('Name of User'), blank=True, max_length=255)
def __str__(self):
return self.username
def get_absolute_url(self):
return reverse('users:detail', kwargs={'username': self.username})
SEX = (
("M","MALE"),
("F","FEMALE"),
)
birthdate = models.DateField(_('Birth Date'), default=django.utils.timezone.now, blank=False)
sex = models.CharField(_('Sex'), choices=SEX, max_length=1, default="M")
isProvider = models.BooleanField(_('Provider'), default=False)
#Using User, not models.Model
class Provider(User):
HAS_BUSINESS = (
('YES','YES'),
('NO','NO'),
)
#Resolving asociation 1:1 to User
#NOTE: AUTH_USER_MODEL = users.User in setting
owner = models.OneToOneField(settings.AUTH_USER_MODEL)
has_business = models.CharField(_('Do you have your own business?'),max_length=2, choices=HAS_BUSINESS, default='NO')
isProvider = True
our forms.py
class ProviderForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
Provider.isProvider = True
class Meta:
model = Provider
fields = '__all__'
exclude = GENERAL_EXCLUSIONS + [
'owner',
]
class ClientForm(SignupForm,ModelForm):
name = forms.CharField(label='Name', strip=True, max_length=50)
lastname = forms.CharField(label='Last Name', strip=True, max_length=50)
class Meta:
model = User
fields = "__all__"
exclude = GENERAL_EXCLUSIONS
def is_active(self):
return False
def __init__(self, *args, **kwargs):
super(ClientForm, self).__init__(*args, **kwargs)
views.py:
class ProviderRegisterView(SignupView):
template_name = 'account/form_provider.html'
form_class = ProviderForm
redirect_field_name = 'next'
view_name = 'registerprovider'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ProviderRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerprovider = ProviderRegisterView.as_view()
#View para el formulario de registro de usuarios clientes
class ClientRegisterView(SignupView):
template_name = 'account/form_client.html'
form_class = ClientForm
redirect_field_name = 'next'
view_name = 'registerclient'
success_url = None
def get_context_data(self, **kwargs):
ret = super(ClienteRegisterView, self).get_context_data(**kwargs)
ret.update(self.kwargs)
return ret
registerclient = ClienteRegisterView.as_view()
finally, our adapter.py:
#Per allauth documentation, settings changed:
#ACCOUNT_ADAPTER = 'projectname.users.adapters.RegisterUserAdapter'
class RegisterUserAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
data = form.cleaned_data
user.first_name = data['name']
user.last_name = data['lastname']
#Saving Client info
user.sex = data['sex']
user.birthdate = data['birthdate']
#Normal allauth saves
user.username = data['username']
user.email = data['email']
if user.isProvider:
p = Provider()
p.owner = user
p.has_business = data['has_business']
if 'password1' in data:
user.set_password(data['password1'])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
#Save user
user.save()
#If it's also a Provider, save the Provider
if user.isProvider:
p.save()
return user
Any help or tips would be greatly appreciated. If I left something out, please let me know. I'm not sure if the problem is in the model itself, the way we represent the form, or the adapter. The way it stands, it doesn't matter what form we use, it's always saved as the base User table (our Client) and the Provider table never gets information saved to it.
With Django's new custom user model, only one user model can be set as settings.AUTH_USER_MODEL. In your example, you can set this to your User model.
Then for the optional provider data, create a separate model that is referenced by OneToOneField from your User model.
class User(AbstractUser):
...
provider = models.OneToOneField(Provider, null=True)
class Provider(models.Model):
...
This is the easiest way to work with multiple user types in Django, given the AUTH_USER_MODEL constraint.
Also, it's best to only subclass abstract models, otherwise you get multitable inheritance which results in hidden implied JOINs, degrading performance.
Finally, you can create the Provider object in your custom form's form.is_valid() method and assign user.provider = provider.
I am trying to log the activities during save operation to track all the changes to user model. my approach is as follows.
class User(AbstractUser):
undergrad_college = models.CharField(max_length=20, choices=COLLEGE_CHOICES)
undergrad_degree = models.CharField(max_length=20, choices=COLLEGE_DEGREES)
postgrad_college = models.CharField(max_length=20, choices=COLLEGE_CHOICES)
postgrad_degree = models.CharField(max_length=20, choices=COLLEGE_DEGREES)
currently_working_on = models.TextField()
previous_work_experience = models.TextField()
previous_internship_experience = models.TextField()
def __str__(self):
return self.username
def save(self, *args, **kwargs):
Log(user=User, actions="Updated profile",
extra={"undergrad_college": self.undergrad_college,
"undergrad_degree": self.undergrad_degree,
"postgrad_college": self.postgrad_college,
"postgrad_degree": self.postgrad_degree,
"currently_working_on": self.currently_working_on,
"previous_work_experience": self.previous_work_experience,
"previous_internship_experience": self.previous_internship_experience
})
super(User, self).save(args, **kwargs)
my views are like this for handling the logging.
class ActivityMixin(LoginRequiredMixin):
def get_context_data(self, **kwargs):
context = super(ActivityMixin, self).get_context_data(**kwargs)
context['activities'] = Log.objects.filter(user=self.request.user)
return context
class IndexListView(ActivityMixin, ListView):
template_name = 'pages/home.html'
model = User
I get this error while performing the update action.
Cannot assign "<class 'users.models.User'>": "Log.user" must be a "User" instance.
Update view is as follows
class UserUpdateView(LoginRequiredMixin, UpdateView):
form_class = UserForm
# we already imported User in the view code above, remember?
model = User
# send the user back to their own page after a successful update
def get_success_url(self):
return reverse("users:detail",
kwargs={"username": self.request.user.username})
def get_object(self, **kwargs):
# Only get the User record for the user making the request
return User.objects.get(username=self.request.user.username)
How to assign the User model instance to the Log function. I cant get this working. I am Django newbie.
Looks like pretty straightforward, replace User with self:
Log(user=User, ...
Log(user=self, ...