mixxorz / django-service-objects Goto Github PK
View Code? Open in Web Editor NEWService objects for Django
License: MIT License
Service objects for Django
License: MIT License
Hi! In Django 4 ugettext_lazy has been removed Now you should use gettext_lazy
Hello! I'm currently handling a case where if a service's execution fails I want to be left with a record of the failure in the database. This means I'm overriding the execute
class method on Service
to remove the following line:
with transaction.atomic():
I wonder if transactionality could be a configurable option on the Service
class?
Hello,
when I try to import a model at the top of my services.py
module I get the following ImportError
:
...
django_1 | File "/app/server/stations/models.py", line 9, in <module>
django_1 | from .services import UpdateAlarmSensorsInDatabase
django_1 | File "/app/server/stations/services.py", line 3, in <module>
django_1 | from .models import BitSensorInformation
django_1 | ImportError: cannot import name 'BitSensorInformation'
services.py
:
from service_objects.services import Service
from .models import BitSensorInformation # causes the error
class UpdateAlarmSensorsInDatabase(Service):
def process(self):
# from .models import BitSensorInformation # this would work
print(BitSensorInformation.objects.all())
models.py
:
from .services import UpdateAlarmSensorsInDatabase
class BitSensorInformation(models.Model):
pass
class AnotherModel(models.Model):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
UpdateAlarmSensorsInDatabase.execute({})
As you can see I need to execute the service object within the model save method. This leads to a cross import issue. Is there any advice you could give me? I try to avoid importing the model at function level.
I Looked that code(service_objects/services.py), this problem solved but i'm getting same error.
Could you please help my why after install latest version its not working?
My assumption: Code updated according to djagno3 but new build not updated on pip packages.
Please help me.
Thank you so much.
Hello contributors!
I want to translate the documentation into Russian. What is the best way to arrange translation files for documentation? Make a fork for the Russian language according to this documentation - readthedocs localization?
We've been using this library for a few weeks and we're really liking it so far -- thank you!
There's one thing that keeps tripping us up: the MultipleModelField
and ListField
s (and maybe others) fail their validation if you pass an empty list. This is counterintuitive -- an empty list is still a list! You can work around it by setting required=False
on the field, but then there's an ambiguity around whether the field is []
or None
.
Would you be open to a PR that changed this behaviour such that empty lists are allowed?
class Sale(models.model):
invoice_number = models.CharField(....)
class SaleItem(models.Model):
sale = models.ForeignKey(Sale)
....
How can i use a service for creating a sale with its respective items ? I'll much appreciate the way you would do it.
The docs show that these fields allow passing in custom validators via the validators
keyword argument (just like regular Django form fields). However, this doesn't actually work!
I'll open a PR with a fix.
Hi @mixxorz what do you think about the idea of having an ExceptionHandler class that could map the InvalidInputErrors to another type of exceptions, for instance, mapping to a relevant error for django rest framework?
Any help on how to use this library with uploaded files? I have it working for where I set the service object to normal Django form fields like forms.CharField
(where the input data comes from a DRF serializers validated_data
).
I've tried using form.FileField,
where the input object is a TemporaryUploadedFile
, but it keep raising the following exception when trying to clean the service object,
service_objects.errors.InvalidInputsError: ({'uploaded_file': ['This field is required.']}, [])
When using large Querysets as the input to MultipleModelField
, the service fails (usually to out of memory / timeouts). This occurs because the Queryset is evaluated in clean
as the MultipleModelField
tries to evaluate that all items in the iteration are of type app.Thing
.
This breaks even if you are handling large querysets with chunking in the process
method.
class DoSomethingToThings(Service):
things = MultipleModelField("app.Thing", required=True)
def process(self):
for thing in Model.objects.all().iterator(chunk_size=2000):
pass # Do something
Not sure how to handle this in MultipleModelField as iterable datatypes like list and tuples in addition to Django Querysets. Also, it has an unsaved check for model objects.
Propose a QuerysetField
that accept Django Querysets of a certain type only. I will make a PR.
Have you all thought about adding the possibility for custom cleaning?
Or maybe some documentation about overriding service_clean
?
I'd be happy to contribute if you all think either of those options are good ideas!
Love the repo by the way! Big fan of the philosophy.
If you set db_transaction = False
on your service and implement a post_process()
method, the post process method does not execute. While examining the code, it makes sense why because the on transaction commit hook is being used. However, the behavior is a bit obtuse especially if you are not familiar with the internals of the library.
The solution I propose is if db_transaction = False
and a post_process()
is implemented, then automatically execute the post_process()
in sequence after the process()
. This would be useful for services where transaction safety is not needed (like an API call, etc.) and would make the feature in parity when db_transaction = True
.
I do believe it's as easy as implementing a finally
statement:
@contextmanager
def _process_context(self):
"""
Returns the context for :meth:`process`
:return:
"""
if self.db_transaction:
with transaction.atomic(using=self.using):
if self.has_on_commit_action:
transaction.on_commit(self.post_process)
yield
else:
try:
yield
finally:
if self.has_on_commit_action:
self.post_process()
Note: I would likely rename has_on_commit_action
to has_post_process_action
since it would not be transaction related.
Alternatively, we could throw an exception if you define a post_process when transaction is false however I think making the feature act in parity is better.
Please let me know your thoughts and I'd be happy to make a PR.
@mixxorz Any chance this could be deployed soon ?. Thanks
A think your concept is really what default Django is missing. In fact, in serveral business-heavy django apps we have used something like your implementation. I think it should be very useful to support DRF serializers (instead of django forms), integrating with update, create methods.
Hi, I have a question, when you call the service say inside a view just like the example you gave, there's going to be a double validation right ? I mean, one from the form itself, and again from the service because its based on forms right ? Is this OK? Or will a feel a delay ? I copy the example from README for making myself clear. Thanks in advance.
# views.py
# Function Based View
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})
# Class Based View
class CreateUserView(ServiceView):
form_class = NewUserForm
service_class = CreateUser
template_name = 'registration/new-user.html'
success_url = '/success/'
At first I thought this project was small enough to not need Sphinx documentation.
That's starting to not be the case.
We need to document:
Things like that.
I've just noticed this project now ;)
The example in the docs (and the README) defines a Service class with form fields and it seems that i have to create a separate form class, isn't it?
So the form fields are defined two times. It's against the DRY concept...
I can see the need for it. For example, if a view form creates several model instances at the same time.
However, this is too costly for simple cases.
Am I missing something?
Currently, the three fields included in django-service-objects are only documented through their docstrings. It would be good to put usage examples of these fields in order to show people their utility.
Fields:
ModelField
MultipleFormField
MultipleModelField
Release 0.3.0 added a new db_transaction
flag which controls whether or not process
will be called inside a transaction. Unfortunately, it was incorrectly implemented meaning that by default, process
wasn't called inside a transaction.
Need to add the using = DEFAULT_DB_ALIAS to the docs.
Currently, if you wanted to accept model instances as inputs to your services, you'd have to use ModelChoiceField
. It works well enough, but it has its quirks. It would be a good idea to a have a custom field that can accept model instances directly. This would fix a few issues I've found when using ModelChoiceField
.
Because ModelChoiceField
just validates using the primary key, it's really easy to mix up models.
person = Person.objects.get(pk=1)
tenant = Tenant.objects.get(pk=1)
SendInvoice.execute({'person': tenant.pk})
# meant to pass in person.pk, but because it's just an integer, it still validates
If we pass in model instances, we can check for its type.
person = Person.objects.get(pk=1)
tenant = Tenant.objects.get(pk=1)
SendInvoice.execute({'person': tenant})
# raises InvalidInputsError, person is not an instance of Person
Because ModelChoiceField
fetches your model via its PK, it inherently requires that your model already be saved before being passed in.
With a custom field, you could accept unsaved model instances as inputs, as long as they're valid.
person = Person(
name='Mitch',
age=24,
)
SendInvoice.execute({'person': person})
# works
person = Person(
name=1234,
)
SendInvoice.execute({'person': person})
# raises InvalidInputsError (name should be string, age is required)
Often I find that I first need to fetch a model instance before I can pass them as inputs. This means that two database queries are made to fetch the same data.
person = Person.objects.get(name='Mitch') # fetch object from the database
SendInvoice.execute({'person': person.pk}) # the form would have to fetch the data from the database again
ModelField
I feel like it would be relatively straightforward to implement a custom field that accepts a model instance as the parameter.
How I imagine the API to be:
class SendInvoice(Service):
person = ModelField('people.Person', allow_unsaved=True)
# to use
person = Person.objects.get(name='Mitch')
SendInvoice.execute({'person': person})
If someone could help with this, that'd be great.
The services shouldn't allow more fields than expected when calling execute
class MyService(Service):
field_a = forms.CharField(...)
Call
MyService.execute({ 'field_y': 1, 'field_a'; 2 })
Expectations:
Raises an exception
'Invalid field provided "field_y": Please provide one of the following fields: ({...})'
Similar case using the ServiceView
.
Hi,
I started using your logic and it helped me a lot!
One thing I thought could be interesting would be create another class of Service which inherits from Modelform. It would get all the fields and validations from the model.
I will fork your repo and try working on it. When it's ready if you like i could send a pr.
Thanks again!
It seems to me that MultipleModelField should accept any iterable such as a Django queryset
, tuple
, and set
-- not just a list
.
I commonly have forms using forms.ModelMultipleChoiceField
which in the cleaned_data returns a queryset. I am having to wrap list
around the data from a forms.ModelMultipleChoiceField
to coerce it into a list for the library.
Views.py:
DuplicateMergeService.execute({
'duplicates':list(form.cleaned_data['duplicates']),
'by': request.user
})
Seems like MultipleModelField
should accept any iterable and not be restricted to list
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.