Skip to content

Commit 78aeeb8

Browse files
author
Peter Kubov
committed
examples
1 parent adae8d2 commit 78aeeb8

File tree

4 files changed

+551
-0
lines changed

4 files changed

+551
-0
lines changed

docs/cores.rst

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,20 @@ Method ``per_save_model`` is called before saving object to database. Body is em
222222

223223
Method ``post_save_model`` is called after saving object to database. Body is empty by default.
224224

225+
**Example: Additional Processing After Save**
226+
227+
Perform additional processing after an object is successfully saved::
228+
229+
class CustomerCore(DjangoUiRestCore):
230+
model = Customer
231+
232+
def post_save_model(self, request, obj, form, change):
233+
super().post_save_model(request, obj, form, change)
234+
235+
# Trigger communication for newly registered customers
236+
if not change: # Only for new customers
237+
trigger_customer_registered_communication(obj)
238+
225239
.. method:: DjangoCore.save_model(request, obj, form, change)
226240

227241
You can rewrite this method if you want to change way how is object saved to database. Default body is::
@@ -273,6 +287,26 @@ improve a speed of your application use this function to create preloading of re
273287

274288
Use this method if you want to change ``list_actions`` dynamically.
275289

290+
**Example: Dynamic Actions Based on State**
291+
292+
Add actions that depend on object state::
293+
294+
class ArticleCore(DjangoUiRestCore):
295+
model = Article
296+
297+
def get_list_actions(self, request, obj):
298+
actions = super().get_list_actions(request, obj)
299+
300+
# Add publish action only for draft articles
301+
if obj.status == 'draft':
302+
actions.append({
303+
'url': self.get_rest_url('publish', pk=obj.pk),
304+
'verbose_name': _('Publish'),
305+
'action': 'publish',
306+
})
307+
308+
return actions
309+
276310

277311
.. method:: DjangoCore.get_default_action(request, obj)
278312

@@ -297,6 +331,21 @@ Every UI core has one place inside menu that addresses one of UI views of a core
297331
Option `show_in_menu` is set to ``True`` by default. If you want to remove core view from menu set this option to
298332
``False``.
299333

334+
**Example Usage**
335+
336+
Hide utility cores from the main navigation::
337+
338+
class CustomerImportCore(DjangoUiRestCore):
339+
"""Internal core for CSV import - not shown in menu"""
340+
model = Customer
341+
show_in_menu = False # Hidden from navigation
342+
menu_url_name = None # No menu link
343+
344+
class CustomerAuditLogCore(DjangoRestCore):
345+
"""API-only core for audit logs"""
346+
model = CustomerAuditLog
347+
show_in_menu = False # REST-only, no UI
348+
300349
.. attribute:: UiCore.view_classes
301350

302351
Option contains view classes that are automatically added to Django urls. Use this option to add new views. Example
@@ -317,6 +366,37 @@ you can see in section generic views (this is a declarative way if you want to r
317366
('reports', r'^/reports/$', MonthReportView),
318367
)
319368

369+
**Complex Example**
370+
371+
Register multiple custom views with specific permissions::
372+
373+
class CustomerCore(DjangoUiRestCore):
374+
model = Customer
375+
376+
view_classes = (
377+
# Custom detail view with tabs
378+
('change', r'^/(?P<pk>[^/]+)/$', CustomerDetailView, False, False),
379+
380+
# Related object management
381+
('add-address', r'^/(?P<customer_pk>[^/]+)/address/add/$', AddressAddView, False, True),
382+
('edit-address', r'^/(?P<customer_pk>[^/]+)/address/(?P<pk>[^/]+)/$', AddressDetailView, False, False),
383+
384+
# Custom actions with parent scoping
385+
('activate', r'^/(?P<pk>[^/]+)/activate/$', CustomerActivateView, False, False),
386+
('deactivate', r'^/(?P<pk>[^/]+)/deactivate/$', CustomerDeactivateView, False, False),
387+
388+
# Bulk operations
389+
('bulk-export', r'^/bulk-export/$', CustomerBulkExportView, False, False),
390+
)
391+
392+
The tuple format is: ``(url_name_suffix, url_pattern, view_class, can_create, can_delete)``
393+
394+
- ``url_name_suffix``: Suffix added to core's base URL name
395+
- ``url_pattern``: Regular expression for URL matching
396+
- ``view_class``: The view class to use
397+
- ``can_create``: Boolean indicating if this view can create objects (default: False)
398+
- ``can_delete``: Boolean indicating if this view can delete objects (default: False)
399+
320400
.. attribute:: UiCore.default_ui_pattern_class
321401

322402
Every view must have assigned is-core pattern class. This pattern is not the same patter that is used with **django**
@@ -419,6 +499,52 @@ Use this method if you want to change ``rest_classes`` dynamically.
419499

420500
Contains code that generates ``rest_patterns`` from rest classes. Method returns an ordered dict of pattern classes.
421501

502+
**Example: Custom REST Endpoints**
503+
504+
Add custom REST endpoints alongside standard CRUD operations::
505+
506+
from django.conf.urls import patterns, url
507+
508+
class CustomerCore(DjangoUiRestCore):
509+
model = Customer
510+
511+
def get_rest_patterns(self):
512+
# Get default patterns (list, detail, create, update, delete)
513+
rest_patterns = super().get_rest_patterns()
514+
515+
# Add custom action endpoints
516+
rest_patterns.update({
517+
'activate': (
518+
r'^/(?P<pk>[^/]+)/activate/$',
519+
self.resource_class,
520+
{'allowed_methods': ('post',)}
521+
),
522+
'deactivate': (
523+
r'^/(?P<pk>[^/]+)/deactivate/$',
524+
self.resource_class,
525+
{'allowed_methods': ('post',)}
526+
),
527+
'bulk-export': (
528+
r'^/bulk-export/$',
529+
self.resource_class,
530+
{'allowed_methods': ('post',)}
531+
),
532+
'stats': (
533+
r'^/stats/$',
534+
CustomerStatsResource,
535+
{'allowed_methods': ('get',)}
536+
),
537+
})
538+
539+
return rest_patterns
540+
541+
This creates additional REST endpoints:
542+
543+
- ``POST /api/customer/{pk}/activate/`` - Activate customer
544+
- ``POST /api/customer/{pk}/deactivate/`` - Deactivate customer
545+
- ``POST /api/customer/bulk-export/`` - Export multiple customers
546+
- ``GET /api/customer/stats/`` - Get customer statistics
547+
422548
HomeUiCore
423549
------------
424550

@@ -729,6 +855,61 @@ Use ``rest_extra_fields`` to define extra fields that is not returned by default
729855
by a HTTP header ``X-Fields`` or a GET parameter ``_fields``. More info you can find in **django-piston** library
730856
documentation. This option rewrites settings inside ``RESTMeta`` (you can find more about it at section #TODO add link).
731857

858+
**Example: API-Only Fields**
859+
860+
Expose computed or aggregated data via REST API without including it in default responses.
861+
862+
Define the fields in your Core::
863+
864+
class CustomerCore(DjangoUiRestCore):
865+
model = Customer
866+
867+
# Fields only available when explicitly requested via API
868+
rest_extra_fields = (
869+
'coin_balance', # Computed field
870+
'applied_promocodes_id', # Filterable field
871+
)
872+
873+
# Allow filtering by extra fields in REST API
874+
rest_extra_filter_fields = (
875+
'applied_promocodes_id',
876+
)
877+
878+
Then define the computed fields in your Resource::
879+
880+
from is_core.utils.decorators import short_description
881+
from pyston.utils.decorators import filter_class
882+
883+
class CustomerResource(DjangoCoreResource):
884+
model = Customer
885+
886+
@short_description(_('Coin Balance'))
887+
def coin_balance(self, obj):
888+
return CoinTransaction.objects.compute_balance_for_customer(obj)
889+
890+
# For filterable extra fields, use @filter_class decorator
891+
@filter_class(AppliedPromocodeIDFilter)
892+
@property
893+
def applied_promocodes_id(self):
894+
return None # Return value not used, filter handles the logic
895+
896+
This pattern is useful for:
897+
898+
- **Performance**: Expensive computed fields are only calculated when needed
899+
- **API flexibility**: Frontend can request additional data without changing default payload
900+
- **Separation**: REST-only data doesn't clutter UI field definitions
901+
902+
Usage example::
903+
904+
# Default API call - excludes extra fields
905+
GET /api/customers/?limit=20
906+
907+
# Request with extra fields
908+
GET /api/customers/?_fields=id,name,coin_balance,applied_promocodes_id
909+
910+
# Filter by extra field (requires @filter_class decorator)
911+
GET /api/customers/?applied_promocodes_id__icontains=PROMO123
912+
732913
.. attribute:: DjangoRestCore.rest_default_guest_fields
733914

734915
``rest_guest_fields`` contains list of fields that can be seen by user that has not permission to see the whole

docs/filters.rst

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,110 @@ Create completely custom filter logic::
123123
manager.add_filter('created_at', DateRangeFilter)
124124
return manager
125125

126+
**Example: Complex Q() Filters**
127+
128+
Create filters that query across multiple tables using Django's Q objects::
129+
130+
from django.db.models import Q
131+
from pyston.filters import Filter
132+
133+
class AppliedPromocodeIDFilter(Filter):
134+
"""Filter customers by applied promocode ID"""
135+
136+
def get_q(self, value, operator_slug, request):
137+
# Search across multiple related tables
138+
return Q(
139+
# Direct promocode usage
140+
customerpromocode__promocode_id__icontains=value
141+
) | Q(
142+
# Promocode via orders
143+
order__customerpromocode__promocode_id__icontains=value
144+
) | Q(
145+
# Promocode via subscriptions
146+
subscription__promocode_id__icontains=value
147+
)
148+
149+
class CustomerCore(DjangoUiRestCore):
150+
model = Customer
151+
152+
def get_list_filter(self, request):
153+
manager = super().get_list_filter(request)
154+
manager.add_filter('applied_promocode_id', AppliedPromocodeIDFilter)
155+
return manager
156+
157+
**Example: Enum-based Filters**
158+
159+
Filter by choices from Python Enums::
160+
161+
from pyston.filters import SimpleFilter
162+
163+
class ChoicesEnumFilter(SimpleFilter):
164+
"""Filter with choices from an Enum"""
165+
166+
def __init__(self, enum_class, **kwargs):
167+
self.enum_class = enum_class
168+
super().__init__(**kwargs)
169+
170+
def get_filter_term(self, value, operator_slug, request):
171+
# Convert enum value to database value
172+
if hasattr(self.enum_class, value):
173+
db_value = getattr(self.enum_class, value).value
174+
return {f'{self.field_name}__exact': db_value}
175+
return {}
176+
177+
def get_choices(self):
178+
# Generate choices from enum
179+
return [(e.name, e.value) for e in self.enum_class]
180+
181+
# Usage with ContentType filtering
182+
from django.contrib.contenttypes.models import ContentType
183+
184+
class PaymentMethodFilter(ChoicesEnumFilter):
185+
def get_filter_term(self, value, operator_slug, request):
186+
# Filter by ContentType for polymorphic relationships
187+
content_types = ContentType.objects.filter(
188+
model__in=['creditcard', 'banktransfer', 'paypal']
189+
)
190+
return {
191+
f'{self.field_name}__in': content_types
192+
}
193+
194+
class OrderCore(DjangoUiRestCore):
195+
model = Order
196+
197+
def get_list_filter(self, request):
198+
manager = super().get_list_filter(request)
199+
manager.add_filter(
200+
'payment_method_type',
201+
PaymentMethodFilter,
202+
enum_class=PaymentMethodEnum
203+
)
204+
return manager
205+
206+
**Example: JSON Field Filters**
207+
208+
Filter by values within JSON fields::
209+
210+
from pyston.filters import Filter
211+
212+
class JSONFieldFilter(Filter):
213+
"""Filter customers by features enabled in JSON field"""
214+
215+
def get_q(self, value, operator_slug, request):
216+
# Filter by JSON array contains
217+
return Q(**{
218+
f'{self.field_name}__contains': [value]
219+
})
220+
221+
class CustomerCore(DjangoUiRestCore):
222+
model = Customer
223+
224+
def get_list_filter(self, request):
225+
manager = super().get_list_filter(request)
226+
# Filter by enabled features in JSON field
227+
manager.add_filter('enabled_features', JSONFieldFilter)
228+
return manager
229+
126230
Common Filter Patterns
127231
----------------------
128232

docs/rests.rst

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,64 @@ Use the REST API with React, Vue, or any other frontend::
619619
return response.json();
620620
}
621621

622+
Background Tasks with Celery
623+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
624+
625+
Use Celery integration for long-running operations::
626+
627+
from is_core.rest.resource import CeleryDjangoCoreResource
628+
629+
class CustomerResource(CeleryDjangoCoreResource):
630+
"""Resource with background task support"""
631+
model = Customer
632+
633+
@short_description(_('Total Orders'))
634+
def total_orders(self, obj):
635+
"""Computed field - available as extra field"""
636+
return obj.orders.count()
637+
638+
@short_description(_('Lifetime Value'))
639+
def lifetime_value(self, obj):
640+
"""Expensive aggregation - computed on demand"""
641+
from django.db.models import Sum
642+
return obj.orders.aggregate(
643+
total=Sum('amount')
644+
)['total'] or 0
645+
646+
def _create_task(self, request, data):
647+
"""Called when object creation is queued as background task"""
648+
from .tasks import create_customer_task
649+
return create_customer_task.delay(data)
650+
651+
# tasks.py
652+
from celery import shared_task
653+
654+
@shared_task
655+
def create_customer_task(customer_data):
656+
"""Background task for customer creation"""
657+
# Perform expensive operations
658+
customer = Customer.objects.create(**customer_data)
659+
660+
# Register with external CRM
661+
external_crm = ExternalCRM()
662+
external_crm.register_customer(customer)
663+
664+
# Send welcome email
665+
send_welcome_email(customer)
666+
667+
return customer.pk
668+
669+
Key features of ``CeleryDjangoCoreResource``:
670+
671+
- Automatically queues long-running operations as Celery tasks
672+
- Returns task ID immediately for tracking
673+
- Client can poll for task status
674+
- Ideal for imports, exports, batch operations
675+
- Built-in error handling and retry logic
676+
677+
.. note::
678+
Use ``CeleryDjangoCoreResource`` when operations take more than 2-3 seconds to complete. This prevents HTTP timeouts and improves user experience.
679+
622680
Best Practices
623681
--------------
624682

0 commit comments

Comments
 (0)