Update MongoDB ReferenceField using Flask-Admin - python

I'm trying to create an admin page from which I can edit what roles a user is member of using MonogDB and Flask-Admin.
models.py
class Role(db.Document, RoleMixin):
name = db.StringField(max_length=80, unique=True)
description = db.StringField(max_length=255)
def __unicode__(self):
return self.name
class User(db.Document, UserMixin):
email = db.StringField(max_length=255)
password = db.StringField(max_length=255)
roles = db.ListField(db.ReferenceField(Role))
admin.py
class UserView(ModelView):
from wtforms.fields import SelectMultipleField
from bson import ObjectId, DBRef
form_overrides = dict(roles=SelectMultipleField)
options = [(g.id, g.name) for g in models.Role.objects()]
# print options
# [(ObjectId('54a72849426c702850d01921'), u'community'),
# (ObjectId('54a72849426c702850d01922'), u'customer')]
form_args = dict(roles=dict(choices=options))
When I select a user role in the Flask-Admin edit_form view and cilck save, following form validation error is shown: '54a72849426c702850d01922' is not a valid choice for this field
What's the correct way to edit/update a ReferenceField ?

Your models look fine. But your ModelView is the problem. I'm using MongoEngine and here is my implementation for them.
class Role(db.Document, RoleMixin):
name = db.StringField(max_length=80, unique=True)
description = db.StringField(max_length=255)
def __unicode__(self):
return self.name
class User(db.Document, UserMixin):
email = db.StringField(max_length=255)
password = db.StringField(max_length=500)
active = db.BooleanField(default=True)
confirmed_at = db.DateTimeField()
roles = db.ListField(db.ReferenceField(Role), default=[])
# Optional to override save method.
def save(self, *args, **kwargs):
self.password = encrypt_password(self.password) # You can encrypt your password before storing in db, as a good practice.
self.confirmed_at = datetime.now()
super(User, self).save(*args, **kwargs)
Here are my model view:
class UserView(ModelView):
can_create = True
can_delete = True
can_edit = True
decorators = [login_required]
column_filters = ('email',)
def is_accessible(self):
return current_user.has_role("admin")
class RoleView(ModelView):
can_create = True
can_delete = True
can_edit = True
decorators = [login_required]
def is_accessible(self):
return current_user.has_role("admin")
You don't have to get all Roles objects explicitly, flask-admin would do it for you. You just have to create Roles first before creating User Object.
Also, you can create an initial user by using flask's before_first_request like this:
#app.before_first_request
def before_first_request():
user_datastore.find_or_create_role(name='admin', description='Administrator')
encrypted_password = encrypt_password('password') # Put in your password here
if not user_datastore.get_user('user#example.com'):
user_datastore.create_user(email='user#example.com', password=encrypted_password)
user_datastore.add_role_to_user('user#example.com', 'admin')
This would help you in updating references correctly.

Related

Flask-Security: How to get / list / print all roles for a given user

I want to list all roles a given user has.
I'm not looking for current_user nor has_role.
The idea is to make an 'edituser.html' where an admin can change/add/remove roles for a given user. For that use case I need to show what roles the user to be edited has.
I've read: Flask Security- check what Roles a User has but I don't understand how to use it in for example a route/view.
My models.py is like this.
class Role(db.Document, RoleMixin):
def __str__(self):
return self.name
name = db.StringField(max_length=80, unique=True)
description = db.StringField(max_length=255)
permissions = db.StringField(max_length=255)
class User(db.Document, UserMixin):
def __str__(self):
return self.username
username = db.StringField(max_length=255)
password = db.StringField(max_length=255)
active = db.BooleanField(default=True)
fs_uniquifier = db.StringField(max_length=64, unique=True)
confirmed_at = db.DateTimeField()
current_login_at = db.DateTimeField()
last_login_at = db.DateTimeField()
current_login_ip = db.StringField(max_length=255)
last_login_ip = db.StringField(max_length=255)
login_count = db.IntField(max_length=255)
roles = db.ListField(db.ReferenceField(Role), default=[])
user_datastore = MongoEngineUserDatastore(db, User, Role)
Here is something I do in my app:
#staticmethod
def get_users():
"""Return list of all users"""
attrs = (
"email",
"active",
"confirmed_at",
"create_datetime",
"update_datetime",
"last_login_at",
"current_login_at",
"current_login_ip",
)
query = current_app.sywuserdb.user_model.query
users = query.all()
# convert to a list of dict of interesting info
rv = []
for user in users:
# copy simple attributes
userdata = {}
for attr in attrs:
userdata[attr] = getattr(user, attr)
userdata["roles"] = [r.name for r in user.roles]
rv.append(userdata)
# users is a list of tuples - convert to list of dict
return {"status": 200, "msgs": [], "data": rv}
Change 'sywuserdb' to your data store ('user_datastore' in your question).
This is called as part of your API - I have an 'admin' blueprint that has the following endpoint defined:
#api.route("/users", methods=["GET"])
#auth_required("token", "session")
#roles_accepted("admin")
def get_users():
rv = get_users()
return flask.jsonify(rv), rv["status"]
Ignore the #staticmethod - that is since I have it as part of a UserFactory class since I have a bunch of admin methods and API to manage users.

how can I set instance in model when using form in django?

in model.py I have this code:
class wallet(models.Model):
User = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
coin= models.PositiveIntegerField(default= 50000)
password = models.CharField(blank=True , max_length = 50)
def __str__(self):
return str(self.wallet)
this is one-to-one relationship between User and wallet
in my form.py I Have this code :
class WalletForm(forms.ModelForm):
User = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = wallet
fields = (
'User',
'coin',
'password'
)
def __init__(self , user , *args,**kwargs):
super().__init__(*args,**kwargs)
self.fields['User'].widget.attrs['value'] = Profile.objects.get(pk = user.id)
self.fields['User'].widget.attrs['readonly'] = True
self.fields['coin'].widget.attrs['value'] = 50000
self.fields['coin'].widget.attrs['readonly'] = True
self.fields['password'].widget.attrs['placeholder'] = 'Wallet Password'
I am trying to make wallet for every user .. the question is :
how can I set User in wallet model to the already user who is logged in my site
when the form submitting ...
thanks
You can utilise the form_valid function in order to set the user before saving the model:
class WalletForm(forms.ModelForm):
...
def form_valid(self, form):
form.instance.user = User.objects.get(user=self.request.user)
form.instance.save(commit=False)
return super().form_valid(form)
Also, you should use attributes in lowercase, ie. change the following from
class wallet(models.Model):
User = ... # From this
user = ... # To this
i think you can exclude the user field from your form
then in the view function do this:
form = SampleForm(request.POST)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()

django how to create hashed password using create_user() method

I have this problem for a month.
I'm using abstractbasemodel and basemanagerto to create a login and signup API using rest framework.
However, when I create a user password, it is saved as raw data since I use set_password() method and custom model manager confuses me...
This is my code :
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id' ,'email' ,'name' ,'password')
extra_kwargs = {
'password':{
'write_only':'True',
'style': {'input_type': 'password'}
}
}
def create(self, validated_data):
user = UserProfile.people.create_user(
email = validated_data['email'],
name = validated_data['name'],
password = validated_data['password']
)
class UserProfileViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserProfileSerializer
queryset = models.UserProfile.people.all()
authentication_classes = (TokenAuthentication, )
permission_classes = (UpdateOwnProfile, )
filter_backends = (SearchFilter, )
search_fields = ('name', 'email')
class UserLoginApiView(ObtainAuthToken):
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
class UserProfileManager(BaseUserManager):
def create_user(self, email, name, password=None):
print("user model manager")
if not email:
raise ValueError('User Must Have an Email Address')
email = self.normalize_email(email)
user = self.model(email=email, name=name )
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, name, password):
user = self.create_user(email, name, password)
user.is_superuser = True
user.is_staff = True
user.save(using=self._db)
return user
class UserProfile(AbstractBaseUser,PermissionsMixin):
email = models.EmailField(max_length=255,unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
people = UserProfileManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name']
def get_full_name(self):
return self.name
def get_short_name(self):
return self.name
def __str__(self):
return self.email
class Profile(models.Model):
user = models.OneToOneField(UserProfile,on_delete=models.CASCADE,relat ed_name="Profile")
location = models.CharField(max_length=100,blank=True,null=True)
bio = models.CharField(max_length=100,blank=True,null=True)
creationDate = models.DateTimeField(auto_now_add=True)
follower = models.ManyToManyField(UserProfile,related_name="Following",blank=True)
class Meta:
verbose_name='Profile'
verbose_name_plural='Profiles'
I also defined auth user model in settings :
AUTH_USER_MODEL='profiles.UserProfile'
to make sure Django uses my custom user model.
I don't know whats wrong as there is no error and only superusers that are created in terminal using manage.py are saved with hashed password.
Users which are created with my viewsets are saved with raw password.
First, I named the model manager "objects" and now, its people but the create user method wont run at all.
You can use django's built in hasher to create hashed password. It can be applied in .create method. First import from django.contrib.auth.hashers import make_password and then modify .create() method,
def create(self, validated_data):
user = UserProfile.people.create_user(
email = validated_data['email'],
name = validated_data['name'],
password = make_password(validated_data['password']) # here
)
return user
Or
if you don't override the .create() method then add the following validate_password method in serializer,
The validate_password is ran, everytime a new object has to be created
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id' ,'email' ,'name' ,'password')
extra_kwargs = {
'password':{
'write_only':'True',
'style': {'input_type': 'password'}
}
}
def validate_password(self, value: str) -> str:
return make_password(value)

I want to write a logic in views.py and save data in serializer

I am making a system saved user data to model.I want to write a part of logic in views.py and a part of save data in serializer.I want to make a system password is changed into hash.Now I wrote codes in views.py,
class InfoViews(viewsets.ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
def create(self,request, *args, **kwargs):
user = Info()
passwd = request.data['password']
md5 = hashlib.md5()
md5.update(passwd.encode('utf-8'))
user.password = md5.hexdigest()
user.save()
return JsonResponse({"data":"data"})
in serializer.py
class InfoSerializer(serializers.ModelSerializer):
created_time = serializers.DateTimeField(required=False)
class Meta:
model = Info
fields = '__all__'
def create(self, validated_data):
user = Info(
email=validated_data['email'],
username=validated_data['username'],
)
user.set_password(validated_data['password'])
user.save()
return user
in models.py
class Info(models.Model):
username = custom_fields.NotEmptyCharField(max_length=100, unique=True)
email = models.EmailField()
password = custom_fields.NotEmptyCharField(max_length=100)
class Meta:
db_table = 'info'
def __str__(self):
return '%s: %s' % (self.username, self.email)
Now whenI tried to save user data to model,django.core.exceptions.ValidationError: ['Can not be empty!'] error happens.What is wrong in my codes?I searched http://www.django-rest-framework.org/api-guide/serializers/ .How should I fix this?
You are not using InfoSerializer() serializer so, remove create() method from that, and change your views.py as below,
class InfoViews(ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
def create(self, request, *args, **kwargs):
serializer = InfoSerializer(request.data).data
serializer.pop('created_time', None)
passwd = serializer['password']
md5 = hashlib.md5()
md5.update(passwd.encode('utf-8'))
serializer['password'] = md5.hexdigest()
Info.objects.create(**serializer)
return JsonResponse({"data": "data"})
My Friendly Suggestion
I don't think this is a good method to acheive so, So changes below will do better (I think so ;))
views.py
class InfoViews(ModelViewSet):
queryset = Info.objects.all()
serializer_class = InfoSerializer
lookup_field = 'id'
serializer.py
import hashlib
class InfoSerializer(serializers.ModelSerializer):
created_time = serializers.DateTimeField(required=False)
def set_password(self, raw_pwd):
md5 = hashlib.md5()
md5.update(raw_pwd.encode('utf-8'))
return md5.hexdigest()
class Meta:
model = Info
fields = '__all__'
def create(self, validated_data):
validated_data['password'] = self.set_password(validated_data['password'])
return super().create(validated_data)
Update
Alternative create() for serializer,
def create(self, validated_data):
validated_data['password'] = self.set_password(validated_data['password'])
user = Info.objects.create(
email=validated_data['email'],
username=validated_data['username'],
password=validated_data['password']
)
# you can avoid large number of assignment statements (as above) by simply calling "super()" method
return user
You're getting a validation error because email is a required field. When you run user.save(), the email value isn't sent, hence the ValidationError.
You should definitely be saving everything in your view, the Serialiser is just a way to change the way the data is presented by DRF.
Also, you really shouldn't be using md5 to save your passwords. Just use the built in Django method: user.set_password(password) - Django will take care of the hashing for you and much more securely.

Django - Creating two user types, where one type can be both

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.

Categories

Resources