{"id":30799052,"url":"https://github.com/dev-muhammad/django-odata","last_synced_at":"2026-05-13T22:36:42.701Z","repository":{"id":312458234,"uuid":"1047573663","full_name":"dev-muhammad/django-odata","owner":"dev-muhammad","description":"OData-compliant APIs in Django with advanced querying and efficient data access","archived":false,"fork":false,"pushed_at":"2025-11-17T09:21:41.000Z","size":54,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T16:22:59.032Z","etag":null,"topics":["django","django-rest-framework","odata"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/django-odata/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dev-muhammad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-30T18:09:42.000Z","updated_at":"2026-03-22T10:51:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"8dc00686-78e2-4ee3-ba8c-34c50952f680","html_url":"https://github.com/dev-muhammad/django-odata","commit_stats":null,"previous_names":["dev-muhammad/django-odata"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/dev-muhammad/django-odata","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-muhammad%2Fdjango-odata","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-muhammad%2Fdjango-odata/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-muhammad%2Fdjango-odata/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-muhammad%2Fdjango-odata/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dev-muhammad","download_url":"https://codeload.github.com/dev-muhammad/django-odata/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dev-muhammad%2Fdjango-odata/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33002991,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"ssl_error","status_checked_at":"2026-05-13T13:14:51.610Z","response_time":115,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","django-rest-framework","odata"],"created_at":"2025-09-05T19:03:16.679Z","updated_at":"2026-05-13T22:36:42.694Z","avatar_url":"https://github.com/dev-muhammad.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Django OData\n\n**Bringing OData Standards to Django** - A comprehensive Django package that implements the OData (Open Data Protocol) specification for REST APIs, enabling standardized data access patterns with powerful querying capabilities.\n\nThis package transforms your Django models into OData-compliant endpoints by seamlessly integrating `drf-flex-fields` and `odata-query`, providing enterprise-grade API functionality with minimal configuration.\n\n## Features\n\n### 🎯 **OData Specification Compliance**\n- **Complete OData v4 Query Support**: Full implementation of OData query options (`$filter`, `$orderby`, `$top`, `$skip`, `$select`, `$expand`, `$count`)\n- **OData Response Format**: Standards-compliant JSON responses with proper `@odata.context` and metadata annotations\n- **Service Metadata**: Built-in `$metadata` endpoint for complete API discovery and client generation\n- **OData Error Handling**: Standardized error responses following OData specifications\n\n### ⚡ **Performance \u0026 Optimization**\n- **Intelligent Query Optimization**: Automatic `select_related()` and `prefetch_related()` application to prevent N+1 queries\n- **Smart Query Translation**: OData filter expressions automatically converted to optimized Django ORM queries\n- **Efficient Data Loading**: Only requested fields are serialized and transmitted\n\n### 🔧 **Developer Experience**\n- **Minimal Configuration**: Transform existing Django models into OData endpoints with just a few lines of code\n- **Django REST Framework Integration**: Seamlessly extends DRF viewsets and serializers\n- **Type Safety**: Proper OData-to-Django field type mapping for all Django field types\n- **Flexible Architecture**: Easy to customize and extend for specific business requirements\n\n## Installation\n\n```bash\npip install django-odata\n```\n\nOr install from source:\n\n```bash\ngit clone https://github.com/dev-muhammad/django-odata.git\ncd django-odata\npip install -e .\n```\n\n## Dependencies\n\n- Django \u003e= 4.2 LTS\n- Python \u003e= 3.8\n- djangorestframework \u003e= 3.12.0\n- drf-flex-fields \u003e= 1.0.0\n- odata-query \u003e= 0.9.0\n\n**Note**: Django 4.2 LTS is supported until April 2026. Please verify that `drf-flex-fields` supports Django 4.2 in your environment, as compatibility may vary between versions.\n\n## Quick Start\n\n### 1. Add to INSTALLED_APPS\n\n```python\nINSTALLED_APPS = [\n    # ... your other apps\n    'rest_framework',\n    'rest_flex_fields',\n    'django_odata',\n]\n```\n\n### 2. Create OData Serializers\n\n```python\nfrom django_odata.serializers import ODataModelSerializer\nfrom .models import BlogPost, Author, Category\n\nclass AuthorSerializer(ODataModelSerializer):\n    class Meta:\n        model = Author\n        fields = ['id', 'name', 'email', 'bio']\n\nclass CategorySerializer(ODataModelSerializer):\n    class Meta:\n        model = Category\n        fields = ['id', 'name', 'description']\n\nclass BlogPostSerializer(ODataModelSerializer):\n    class Meta:\n        model = BlogPost\n        fields = ['id', 'title', 'content', 'status', 'created_at']\n        expandable_fields = {\n            'author': (AuthorSerializer, {}),\n            'categories': (CategorySerializer, {'many': True}),\n        }\n```\n\n### 3. Create OData ViewSets\n\n```python\nfrom django_odata.viewsets import ODataModelViewSet\nfrom .models import BlogPost, Author, Category\nfrom .serializers import BlogPostSerializer, AuthorSerializer, CategorySerializer\n\nclass BlogPostViewSet(ODataModelViewSet):\n    queryset = BlogPost.objects.all()\n    serializer_class = BlogPostSerializer\n\nclass AuthorViewSet(ODataModelViewSet):\n    queryset = Author.objects.all()\n    serializer_class = AuthorSerializer\n\nclass CategoryViewSet(ODataModelViewSet):\n    queryset = Category.objects.all()\n    serializer_class = CategorySerializer\n```\n\n### 4. Configure URLs\n\n```python\nfrom django.urls import path, include\nfrom rest_framework.routers import DefaultRouter\nfrom .views import BlogPostViewSet, AuthorViewSet, CategoryViewSet\n\nrouter = DefaultRouter()\nrouter.register(r'posts', BlogPostViewSet)\nrouter.register(r'authors', AuthorViewSet)\nrouter.register(r'categories', CategoryViewSet)\n\nurlpatterns = [\n    path('odata/', include(router.urls)),\n]\n```\n\n## Usage Examples\n\n### Basic Queries\n\n```bash\n# Get all blog posts\nGET /odata/posts/\n\n# Get a specific blog post\nGET /odata/posts/1/\n\n# Get first 10 posts\nGET /odata/posts/?$top=10\n\n# Skip first 20 posts, get next 10\nGET /odata/posts/?$skip=20\u0026$top=10\n```\n\n### Filtering\n\n```bash\n# Get published posts\nGET /odata/posts/?$filter=status eq 'published'\n\n# Get posts with more than 100 views\nGET /odata/posts/?$filter=view_count gt 100\n\n# Get posts created this year\nGET /odata/posts/?$filter=year(created_at) eq 2024\n\n# Complex filter\nGET /odata/posts/?$filter=status eq 'published' and view_count gt 50\n```\n\n### Sorting\n\n```bash\n# Sort by creation date (newest first)\nGET /odata/posts/?$orderby=created_at desc\n\n# Sort by title alphabetically\nGET /odata/posts/?$orderby=title asc\n\n# Multiple sort criteria\nGET /odata/posts/?$orderby=status desc,created_at desc\n```\n\n### Field Selection\n\n```bash\n# Select specific fields (OData standard)\nGET /odata/posts/?$select=id,title,status\n\n# If no $select specified, returns all available fields\nGET /odata/posts/\n\n# Omit specific fields (legacy feature)\nGET /odata/posts/?omit=content\n```\n\n### Field Expansion\n\n```bash\n# Include author information (automatically adds 'author' to selected fields)\nGET /odata/posts/?$expand=author\n\n# Include multiple related fields\nGET /odata/posts/?$expand=author,categories\n\n# When using $expand, expanded fields are automatically selected\nGET /odata/posts/?$expand=author\n# Returns: all fields + author (with expanded data)\n\n# Explicit field selection with expansion\nGET /odata/posts/?$select=id,title\u0026$expand=author\n# Returns: id, title, author (with expanded data)\n\n# Nested field selection in expanded properties (OData standard)\nGET /odata/posts/?$expand=author($select=name,bio)\n# Returns: all fields + author (with only name and bio)\n\n# Multiple nested expansions\nGET /odata/posts/?$expand=author($select=name,bio),categories($select=id,name)\n# Returns: all fields + author (name,bio) + categories (id,name)\n\n# Mixed simple and nested expansions\nGET /odata/posts/?$expand=author($select=name),categories,tags($select=name)\n# Returns: all fields + author (name only) + categories (all fields) + tags (name only)\n\n# Combine explicit selection with nested expansions\nGET /odata/posts/?$select=id,title\u0026$expand=author($select=name,bio)\n# Returns: id, title, author (with name and bio only)\n```\n\n### Automatic Query Optimization\n\nThe package automatically optimizes database queries when using `$expand` to prevent N+1 query problems:\n\n```bash\n# This request automatically applies prefetch_related('posts')\nGET /odata/authors/?$expand=posts($select=id,title)\n\n# This request automatically applies select_related('author') \nGET /odata/posts/?$expand=author($select=name,bio)\n```\n\n**Optimization Rules:**\n- **Forward relationships** (ForeignKey, OneToOne): Uses `select_related()` for efficient JOINs\n- **Reverse relationships** (reverse ForeignKey, ManyToMany): Uses `prefetch_related()` for separate optimized queries\n- **No manual optimization needed**: The package detects relationship types and applies the appropriate optimization automatically\n\n### Counting\n\n```bash\n# Get total count along with results\nGET /odata/posts/?$count=true\n\n# Get count of filtered results\nGET /odata/posts/?$filter=status eq 'published'\u0026$count=true\n```\n\n### Metadata\n\n```bash\n# Get service metadata\nGET /odata/posts/$metadata\n\n# Get service document\nGET /odata/\n```\n\n## Advanced Usage\n\n### Custom ViewSets\n\n```python\nfrom django_odata.viewsets import ODataModelViewSet\n\nclass CustomBlogPostViewSet(ODataModelViewSet):\n    queryset = BlogPost.objects.all()\n    serializer_class = BlogPostSerializer\n    \n    def get_queryset(self):\n        \\\"\\\"\\\"Add custom filtering logic.\\\"\\\"\\\"\n        queryset = super().get_queryset()\n        \n        # Only show published posts to non-staff users\n        if not self.request.user.is_staff:\n            queryset = queryset.filter(status='published')\n        \n        return queryset\n```\n\n### Factory Functions\n\n```python\nfrom django_odata.serializers import create_odata_serializer\nfrom django_odata.viewsets import create_odata_viewset\n\n# Create serializer automatically\nBlogPostSerializer = create_odata_serializer(\n    BlogPost,\n    fields=['id', 'title', 'content', 'status'],\n    expandable_fields={\n        'author': ('myapp.serializers.AuthorSerializer', {}),\n    }\n)\n\n# Create viewset automatically\nBlogPostViewSet = create_odata_viewset(BlogPost, serializer_class=BlogPostSerializer)\n```\n\n### Query Builder\n\n```python\nfrom django_odata.utils import ODataQueryBuilder\n\n# Build queries programmatically\nquery = (ODataQueryBuilder()\n         .filter(\"status eq 'published'\")\n         .filter(\"view_count gt 100\")\n         .order('created_at', desc=True)\n         .limit(20)\n         .select('id', 'title', 'author')\n         .expand('author')\n         .build())\n\n# query now contains the query parameters dictionary\n```\n\n## OData Query Options Reference\n\n| Option | Description | Example |\n|--------|-------------|---------|\n| `$filter` | Filter results based on conditions | `$filter=status eq 'published'` |\n| `$orderby` | Sort results | `$orderby=created_at desc` |\n| `$top` | Limit number of results | `$top=10` |\n| `$skip` | Skip number of results | `$skip=20` |\n| `$select` | Choose specific fields | `$select=id,title,status` |\n| `$expand` | Include related data | `$expand=author,categories` or `$expand=author($select=name,bio)` |\n| `$count` | Include total count | `$count=true` |\n\n### Filter Operators\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| `eq` | Equal | `status eq 'published'` |\n| `ne` | Not equal | `status ne 'draft'` |\n| `gt` | Greater than | `view_count gt 100` |\n| `ge` | Greater than or equal | `rating ge 4.0` |\n| `lt` | Less than | `view_count lt 50` |\n| `le` | Less than or equal | `rating le 3.0` |\n| `and` | Logical AND | `status eq 'published' and featured eq true` |\n| `or` | Logical OR | `status eq 'published' or status eq 'featured'` |\n| `not` | Logical NOT | `not (status eq 'draft')` |\n\n### String Functions\n\n| Function | Description | Example |\n|----------|-------------|---------|\n| `contains` | String contains | `contains(title,'django')` |\n| `startswith` | String starts with | `startswith(title,'How to')` |\n| `endswith` | String ends with | `endswith(title,'Guide')` |\n| `length` | String length | `length(title) gt 10` |\n| `tolower` | Convert to lowercase | `tolower(title) eq 'django guide'` |\n| `toupper` | Convert to uppercase | `toupper(status) eq 'PUBLISHED'` |\n\n### Date Functions\n\n| Function | Description | Example |\n|----------|-------------|---------|\n| `year` | Extract year | `year(created_at) eq 2024` |\n| `month` | Extract month | `month(created_at) eq 12` |\n| `day` | Extract day | `day(created_at) eq 25` |\n| `hour` | Extract hour | `hour(created_at) eq 14` |\n| `minute` | Extract minute | `minute(created_at) eq 30` |\n| `second` | Extract second | `second(created_at) eq 45` |\n\n## Configuration\n\nAdd optional settings to your Django settings:\n\n```python\n# Optional django-odata settings\nDJANGO_ODATA = {\n    'SERVICE_ROOT': '/odata/',\n    'MAX_PAGE_SIZE': 1000,\n    'DEFAULT_PAGE_SIZE': 50,\n    'ENABLE_METADATA': True,\n    'ENABLE_SERVICE_DOCUMENT': True,\n}\n```\n\n## Response Format\n\n### Collection Response\n\n```json\n{\n  \"@odata.context\": \"http://example.com/odata/$metadata#posts\",\n  \"@odata.count\": 150,\n  \"value\": [\n    {\n      \"id\": 1,\n      \"title\": \"Introduction to Django\",\n      \"status\": \"published\",\n      \"author\": {\n        \"id\": 1,\n        \"name\": \"John Doe\",\n        \"email\": \"john@example.com\"\n      }\n    }\n  ]\n}\n```\n\n### Single Entity Response\n\n```json\n{\n  \"@odata.context\": \"http://example.com/odata/$metadata#posts/$entity\",\n  \"id\": 1,\n  \"title\": \"Introduction to Django\",\n  \"content\": \"This is a comprehensive guide...\",\n  \"status\": \"published\",\n  \"created_at\": \"2024-01-15T10:30:00Z\"\n}\n```\n\n### Error Response\n\n```json\n{\n  \"error\": {\n    \"code\": \"BadRequest\",\n    \"message\": \"The query specified in the URI is not valid.\"\n  }\n}\n```\n\n## Testing\n\nRun the test suite:\n\n```bash\n# Install test dependencies\npip install -e .[dev]\n\n# Run tests\npytest\n\n# Run tests with coverage\npytest --cov=django_odata\n```\n\n## Example Project\n\nSee the `example/` directory for a complete Django project demonstrating all features:\n\n```bash\ncd example/\npip install -r requirements.txt\npython manage.py migrate\npython manage.py runserver\n```\n\nThen visit:\n- http://localhost:8000/odata/posts/ - Blog posts endpoint\n- http://localhost:8000/odata/posts/$metadata - Metadata\n- [http://localhost:8000/odata/posts/?$filter=status eq 'published'\u0026$expand=author](http://localhost:8000/odata/posts/?$filter=status eq 'published'\u0026$expand=author) - All published posts expanded with author\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n\n## Credits\n\n- Built on top of [Django REST Framework](https://www.django-rest-framework.org/)\n- Uses [drf-flex-fields](https://github.com/rsinger86/drf-flex-fields) for dynamic field selection\n- Uses [odata-query](https://github.com/gorilla-co/odata-query) for OData query parsing\n\n## Changelog\n\n### v0.1.0 (2025-08-30)\n- Initial release\n- Full OData query support ($filter, $orderby, $top, $skip, $select, $expand, $count)\n- Dynamic field selection and expansion\n- Metadata endpoints ($metadata, service document)\n- Comprehensive test suite\n- Example application\n- Support for Django 4.2 LTS and Python 3.8+\n\n### v0.1.1 (2025-11-17)\n- Lazy import implemented\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-muhammad%2Fdjango-odata","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdev-muhammad%2Fdjango-odata","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdev-muhammad%2Fdjango-odata/lists"}