Rails share database with django
You can share rails user table (generated by devise gem) on django.
Code
User class based on AbstractBaseUser.
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
class UserManager(BaseUserManager):
def create_user(self, email, name, password=None):
if not email:
raise ValueError(_('Users must have an email address'))
user = self.model(email=self.normalize_email(email), name=name)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, nickname, last_name, first_name, password):
pass # ref: createsuperuser is hard coded
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=70, unique=True)
name = models.CharField(max_length=70)
roles = ArrayField(models.CharField(max_length=70), default=[])
encrypted_password = models.CharField(max_length=128)
last_login = models.DateTimeField(_('last login'), blank=True, null=True, db_column='last_sign_in_at')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'password']
objects = UserManager()
class Meta:
managed = False
db_table = 'users'
@property
def password(self):
return f'devise{self.encrypted_password}'
@password.setter
def password(self, var):
self.encrypted_password = var[6:]
@property
def is_superuser(self):
print('TODO calculate is_superuser from roles')
return True
@property
def is_active(self):
print('TODO calculate is_superuser from roles')
return True
@property
def is_staff(self):
return True
Very simple form for admin site. (forms.py)
from django import forms
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from .models import User
class UserCreationForm(forms.ModelForm):
password = forms.CharField(label='Password', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('email', )
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
password = ReadOnlyPasswordHashField()
class Meta:
model = User
fields = ('email', 'password')
def clean_password(self):
return self.initial["password"]
Simple admin class. (admin.py)
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .forms import UserChangeForm, UserCreationForm
from .models import User
class UserAdmin(BaseUserAdmin):
form = UserChangeForm
add_form = UserCreationForm
list_display = ('email', 'name', )
list_filter = ('roles',)
search_fields = ('email',)
ordering = ('email',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
)
admin.site.register(User, UserAdmin)
Hasher class (modified from BCryptSHA256PasswordHasher).
import hashlib
from django.contrib.auth.hashers import BasePasswordHasher
from django.utils.crypto import constant_time_compare
class HERAPasswordHasher(BasePasswordHasher):
algorithm = "devise"
digest = hashlib.sha256
library = ("bcrypt", "bcrypt")
rounds = 10
def salt(self):
bcrypt = self._load_library()
return bcrypt.gensalt(self.rounds, b'2a') # devise uses 2a
def encode(self, password, salt):
bcrypt = self._load_library()
password = password.encode()
data = bcrypt.hashpw(password, salt)
return "%s%s" % (self.algorithm, data.decode('ascii'))
def decode(self, encoded):
algorithm, algostr, work_factor, data = encoded.split('$', 4)
assert algorithm == self.algorithm
return {
'algorithm': algorithm,
'algostr': algostr,
'checksum': data[22:],
'salt': data[:22],
'work_factor': int(work_factor),
}
def verify(self, password, encoded):
algorithm, data = encoded.split('$', 1)
assert algorithm == self.algorithm
encoded_2 = self.encode(password, b'$'+data.encode('ascii')[0:28]) # devise uses 29 chars as salt
return constant_time_compare(encoded, encoded_2)
Now you can modify settings. (I’ve put files above in ruser
app.)
AUTH_USER_MODEL = 'ruser.User'
PASSWORD_HASHERS = [
'ruser.hashers.HERAPasswordHasher',
]
Test
You can test in rails console (bundle exec rails console
) and django console (python manager.py shell
).
# create user in rails
User.create!({email: "YOUR_EMAIL", password: "YOUR_PASSWORD",
password_confirmation: "YOUR_PASSWORD", name: "YOUR_NAME"})
# login in rails
ApplicationController.allow_forgery_protection = false
app.post('/users/login', {params: {"user"=>{"email"=>"YOUR_EMAIL", "password"=>"YOUR_PASSWORD"}}})
# login in django
from django.contrib.auth import authenticate
user = authenticate(username='YOUR_EMAIL', password='YOUR_PASSWORD')
# create user in django
from herauser.models import User
User.objects.create_user('YOUR_EMAIL', name='YOUR_NAME', password='YOUR_PASSWORD')
# update password in django
from herauser.models import User
u = User.objects.filter(email='YOUR_EMAIL')[0]
u.set_password('YOUR_PASSWORD')
u.save()
korean original post is https://tech.jinto.pe.kr/post/2021-05-23-%EB%A0%88%EC%9D%BC%EC%A6%88-%EC%82%AC%EC%9A%A9%EC%9E%90%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%9E%A5%EA%B3%A0/