Service objects for Django
This is a small library providing a Service
base class to derive your service objects from. What are service objects? You can read more about the whys and hows in this blog post, but for the most part, it encapsulates your business logic, decoupling it from your views and model methods. Put your business logic in service objects.
Let's say you want to register new users. You could make a CreateUser
service.
class CreateUser(Service):
email = forms.EmailField()
password = forms.CharField(max_length=255)
subscribe_to_newsletter = forms.BooleanField(required=False)
def process(self):
email = self.cleaned_data['email']
password = self.cleaned_data['password']
subscribe_to_newsletter = self.cleaned_data['subscribe_to_newsletter']
user = User.objects.create_user(
username=email, email=email, password=password)
if subscribe_to_newsletter:
newsletter = Newsletter.objects.get()
newsletter.subscribers.add(user)
newsletter.save()
WelcomeEmail.send(user, is_subscribed=subsribe_to_newsletter)
return user
Notice that it's basically a Django form but with a process
method. This method gets called when you call execute()
on the process. If your inputs are invalid, it raises InvalidInputsError
.
Here's how you use it:
CreateUser.execute({
'email': 'kvothe@edemaruh.com',
'password': 'doorsofstone',
'subscribe_to_newsletter': True,
})
Now you can use it anywhere.
In your views
# views.py
def create_user_view(request):
form = NewUserForm()
if request.method == 'POST':
form = NewUserForm(request.POST)
if form.is_valid():
try:
CreateUser.execute(request.POST)
return redirect('/success/')
except Exception:
form.add_error(None, 'Something went wrong')
return render(request, 'registration/new-user.html', {'form': form})
A management command
# management/commands/create_user.py
class Command(BaseCommand):
help = "Creates a new user"
def add_arguments(self, parser):
parser.add_argument('email')
parser.add_argument('password')
def handle(self, *args, **options):
user = CreateUser.execute(options)
self.stdout.write(f'New user created : {user.email}')
In your tests
class CreateUserTest(TestCase):
def test_create_user(self):
inputs = {
'email': 'kvothe@edemaruh.com',
'password': 'do0r$0f$stone42',
'subscribe_to_newsletter': True,
}
CreateUser.execute(inputs)
user = User.objects.get()
self.assertEqual(user.email, inputs['email'])
newsletter = Newsletter.objects.get()
self.assertIn(user, newsletter.subscribers.all())
And anywhere you want. You can even execute services inside other services. The possibilities are endless!
Docs can be found on readthedocs.
If you have any questions about service objects, you can tweet me @mixxorz.