Giter Club home page Giter Club logo

sharing-configs's Introduction

Sharing Configs for Django

Version:0.1.2
Source:https://github.com/maykinmedia/sharing-configs
Keywords:django, github
PythonVersion:3.7, 3.8, 3.9

Build status Coverage status black

python-versions django-versions pypi-version

A reusable Django app to export and import resources using Sharing Configs API.

Developed by Maykin Media B.V..

Features

  • provides client to interact with Sharing Configs API
  • easy download and upload of resources in the Django admin

Installation

  1. Install from PyPI
pip install sharing-configs
  1. Add sharing_configs to the INSTALLED_APPS setting.
  2. Run manage.py migrate to create the database tables for sharing-configs
  3. In the admin page of SharingConfigsConfig configure access to the Sharing Configs API

Usage

The Sharing Config Library provides two mixins to add into your ModelAdmin class to enable the import/export of objects through the Django admin:

  • SharingConfigsImportMixin - to import objects
  • SharingConfigsExportMixin - to export objects
  • SharingConfigsMixin - to import and export objects

The mixins provide custom admin views and request Sharing Configs API under the hood. You will need to override the get_sharing_configs_import_data and get_sharing_configs_export_data functions and implement your own import/export behaviour.

You can furthermore override the import/export forms that are used by overriding the class variables sharing_configs_export_form and sharing_configs_import_form.

from sharing_configs.admin import SharingConfigsMixin

class SomeObjectAdmin(SharingConfigsMixin, admin.ModelAdmin):

    sharing_configs_export_form = SomeObjectExportForm  # Defaults to sharing_configs.forms.ExportToForm
    sharing_configs_import_form = SomeObjectImportForm  # Defaults to sharing_configs.forms.ImportForm

    def get_sharing_configs_export_data(self, obj: object) -> bytes:
        """
        Convert ``SomeObject`` to JSON or something.
        """
        # Your code...

    def get_sharing_configs_import_data(self, content: bytes) -> object:
        """
        Convert JSON (or whatever was exported by function above) to
        ``SomeObject``.
        """
        # Your code...

Example

We can use the Sharing Configs library to exchange color-themes for the Django admin with other users. For this, we need a model that stores the color-theme, and use the Sharing Configs mixins to import and export the color-theme.

Create two models: Configuration and Theme

# models.py

from django.db import models
from solo.models import SingletonModel


class Configuration(SingletonModel):
    """Configuration that holds the current theme"""

    theme = models.ForeignKey("Theme", on_delete=models.SET_NULL, null=True, blank=True)


class Theme(models.Model):
    """All attributes used for theming."""

    name = models.CharField("name", max_length=100)
    primary = models.CharField("primary color", max_length=7)
    secondary = models.CharField("secondary color", max_length=7)
    accent = models.CharField("accent color", max_length=7)
    primary_fg = models.CharField("primary foreground color", max_length=7)

Register the Theme model in the admin and include our two mixins to introduce the UI to import and export objects, in this case, themes. Sharing Configs does not know how to import or export your model, so you will need to write this yourself. You can override the methods introduced by the mixins: get_sharing_configs_export_data and get_sharing_configs_import_data

# admin.py

import json

from django.contrib import admin
from django.forms.models import model_to_dict
from django.shortcuts import get_object_or_404

from sharing_configs.admin import SharingConfigsMixin

from .models import Configuration, Theme


class ThemeAdmin(SharingConfigsMixin, admin.ModelAdmin):

    def get_sharing_configs_export_data(self, obj: object) -> bytes:
        """Convert the theme to JSON."""
        theme = get_object_or_404(Theme, id=obj.id)
        theme_dict = model_to_dict(theme)
        theme_dict.pop("id", None)
        dump_json_theme = json.dumps(cleaned_theme_dict, sort_keys=True, default=str)
        return dump_json_theme.encode("utf-8")

    def get_sharing_configs_import_data(self, content: bytes) -> object:
        """
        Convert JSON to a new theme instance. Typically, the JSON that is
        read here is the same as that the JSON generated by the above
        function.
        """
        decoded_content = content.decode("utf-8")
        theme_dict = json.loads(decoded_content)
        return ColorTheme.objects.create(**theme_dict)

That takes care of the import and export functionality for exchaning color-themes. To make it actually working, we complete this example with some additional code. Create a context_processors.py file, to pass the currently configured theme to the template context:

def theme(request:object)->dict:
    """
    Create a dictionary of color variables to pass to the base_site.html Django admin page
    """
    conf = Configuration.get_solo()

    return {
        "theme": conf.theme
    }

Finally, pass the theme context variables to an overriden base_site.html in the templates folder.

{# admin/base_site.html #}
{% extends "admin/base_site.html" %}

{% block extrastyle %}
    {% if theme %}
        <style type="text/css">
            :root {
                --primary: {{ theme.primary }};
                --secondary:{{ theme.secondary }};
                --accent:{{ theme.accent }};
                --primary_fg:{{ theme.primary_fg }};
            }
        </style>
    {% endif %}
{% endblock %}

Now you can choose an available color-theme via the configuration inside the Django admin. Ofcourse, this will really shine when you configure a proper Sharing Configs API to exchange themes with eachother!

sharing-configs's People

Contributors

frekkensnork avatar joeribekker avatar annashamray avatar alextreme avatar

Watchers

 avatar James Cloos avatar  avatar  avatar

sharing-configs's Issues

As user of SC API, I retrieve/store more details about the item I want to import.

Thema / Theme

Other

Description

We should track more meta data about items.

  • Keep track of downloads
  • Allow star-rating (1 to 5) of items (setter: max 1 per API-key)
  • Allow for a description of the item

We should figure out how to store this meta data in Github.

Added value

More context makes it easier to decide which items you want to import from the community.

Additional context

No response

Add API resource to retrieve all folders for given label

GET /config/{label}/folder?permission=[all,read,write]

[{
  "parent": null,
  "name": "community-concepts",
  "permission": "write",
  "children": [{
    "name": "productaanvraag",
  }]
},
{
  "parent": null,
  "name": "community-standards",
  "permission": "read",
  "children": []
}]

And please, imagine there is pagination above!

We need this model to get the allowed root folders.

class RootPathConfig(models.Model):
  config = FK(Config)
  name = models.CharField()
  permission = models.ChoiceField()

As developer, I want to override the export/import form

Tasks

  • In the SharingConfigsExportMixin, add a class variable: sharing_configs_export_form = "sharing_configs.forms.SharingConfigsExportForm and similar for import form.
  • Refactor the mixin in such a way to use the form class defined in the class variable.

As a developer I want API repo to be public

Prepare API repo to become public:

  • update README and INSTALL files
  • add CONTRIBUTING, AUTHORS etc. files
  • add templates for github issues
  • research dependabot alerts and fix if possible

Sharing configs conflicts with django-import-export

Thema / Theme

Other

Description

It currently isn't possible to add sharing-configs to an admin that also has django-import-export enabled and keep everything working.

  • url paths overlap, eg: /admin/foo/bar/import/ is used by both import-export and sharing-configs. The solution would be to give the sharing-configs URLs a prefix
  • both admin mixin templates overlap, causing only one or the other's object-tools to be shown (the import buttons)

Added value

No response

Additional context

No response

Create sharing configs API as middleware between the app/library and the Github repo

We'll create a small component that exposes a minimal api, that an app can access, so the component makes the PR for the app without exposing any GitHub credentials in the app.

  • Create new component/repo with just an API and admin

  • Add model to configure GitHub access. Make sure the GitHub configs can have a label and multiple GitHub configs can be created (no solo model)

  • Add support for plugable file storage backends (handlers)

  • Add debug handler for testing purposes

  • Add token-based authorization for API

  • Add tests for API and CI to run tests

  • The API should allow us to: 1) retrieve all labels, 2) share a config by passing label, config, user full name, organisation and description, 3) retrieve list configs filtered by label, 4) retrieve shared configs Upd. moved to #19

  • When sharing a config via the API, the API should create a commit with the configured credentials and the commit message should say the username, organisation name and description

Finish up community files

  • Add SECURITY.rst
  • Complete the README with usage, link to API project and mention it implements that API spec
  • Add GitHub issue templates

As developer I want to have Config model and admin pluggable

  • For now there is one model Config which stores backend specific parameters for all handlers. It should be refactored, that backed-specific parameters should be pluggable just like handlers themselves.
  • ConfigTypes choices should be also extensible
  • DebugHandler should be moved to the separate folder in the 'contrib'

As user, I want my export to be sent to the Sharing Configs API

After #12 and #3, this ticket can be done.

Tasks

  • Create a simple client class, using requests to connect to the Sharing Configs API. See pseudo code below
  • In issue 12, we refactored the ExportToGithubForm.save method. This should call the created client to send the export. Something like: scc = SharingConfigsClient(); scc.export(data)

Pseudo client code

class SharingConfigsClient:
  def __init__(self):
    self.config = SharingConfigsConfig.objects.get_solo()

  def export(self, data):
    headers = {"content-type": "application/json", "authorization": f"Token {self.config.api_key}"}
    return requests.post(self.config.api_endpoint, data, headers=headers)

As developer, I want to allow for more than just Github exports

We can also share configurations via Pleio, Gitlab, Dropbox, Microsoft Drive, etc. These can be "plugins".

Tasks

  • Make sure there is basic machinery (baseclass) to support the concrete actions of each plugin
  • Create contrib folder and add Github as the first contrib plugin to export to Github.

As a developer I want better test coverage

Tests need some love

  • In test_auth.py check please that the redirect goes to login page
  • test_true.py can be removed, it's not needed anymore
  • I don't understand the point of tests in test_urls.py and I think they are not needed. But maybe @joeribekker has another opinion
  • There are no tests for export admin page. Please include following:
    • the admin detail page includes 'export' button which has a link to the export page
    • export form folder field has expected choices
    • request export page and successfully submit form
    • there is a connection error when the form is submitted
    • Please include in all tests with mocks the check for the parameters the mock was called (for patch it's
  • I have questions for test_mock_import.py tests:
    • What happens in test_import_valid_form test? Why do you send request body with HTTP GET?
    • Please include test with requesting import page and successfully submitting form
    • Please include test with a connection error when the import form is submitted
    • Please include test that adminlist page has 'import' button with the link to the import page
  • For all tests where Sharing Config API is requested (and mocked) please include the check for the mock parameters. We need to make sure that the API is requested in the expected way.

As a develop I want to remove duplicated functions

It looks like sharing_configs/utils.py has function which do the same as methods in sharing_configs/client_util.py:

  • get_folders_from_api is doing the same as SharingConfigsClient.get_folder
  • get_files_in_folder_from_api is doing the same as SharingConfigsClient.get_files

Could you remove them?

Bugs discovered while integrating with objecttypes api

Describe the bug

Import:

  • get_sharing_configs_import_data doesn't have content parameter. It's impossible to import file without knowing its content
  • get_sharing_configs_import_data is never called inside SharingConfigsImportMixin , so the hook designed by the lib user is actually never used
  • in the import.html there is a url admin:auth_user_import which doesn't not exist
  • in the import.html there is a url admin:auth_user_ajax which doesn't exist
  • in get_imported_folders_choices for import you don't need permission 'read'. You need to show folders with all permissions, not read-only.
  • get_list_folders_url, get_folder_files_url, get_import_url, get_export_url in SharingConfigsClient miss 'config' part of the url

Export:

  • In sharing_configs_export_view obj.username is used which doesn't exist
  • Why is permission param in SharingConfigsClient.get_folders treated as dict when you fill it with string?
  • content is not base64 encoded, which can't be processed by API
  • (not crit) On the export page when there is a validation error the header becomes "Import from community" (see the screenshot below)
  • (not crit) On the export page the validation error starts with: "Import of object failed: "

Steps to reproduce

No response

Expected behavior

No response

Additional context

image

As developer, I want to easily connect my export function to sharing-configs

For this, we'll create a mixin. A mixin already exists but it was created before we decided to abstract it away from Github, so we need to refactor it.

Tasks

  • Split up sharing_configs/admin.py GitHubExportImportMixin into SharingConfigsExportMixin and SharingConfigsImportMixin.
  • In the SharingConfigsExportMixin, add a function:
    get_sharing_configs_export_data(self, obj, **form_kwargs):
        raise NotImplemented
    
  • In the testapp, create an admin class that inherits this mixin, and override the get_sharing_configs_export_data function to return the data from #10
  • Refactor the SharingConfigsExportMixin.export_to_github_view to be called sharing_configs_export_view and make sure to pass self.get_sharing_configs_export_data to the form: ExportToGithubForm(request.POST, instance=obj, initial=initial, export_func=self.get_sharing_configs_export_data)
  • Refactor the ExportToGithubForm.__init__ method to catch and store the get_sharing_configs_export_data function
  • Refactor the ExportToGithubForm.save method to call the export function (and do nothing with it for now)
  • Rename all Github-related variables/function to something without Github :)

As a developer I want examples how to use the lib

Thema / Theme

Other

Description

  • Please update "Usage" section of the README file with examples how to use the library for import and export
  • Please add more information in the docstrings of get_sharing_configs_import_data and get_sharing_configs_export_data

Example

As example we're going to use "themes", that changes the appearance of the Django admin.
For simplicity, a color theme has just a few (css) properties and a name. This "theme" can be exported and imported via this library.

  • Add a "Theme" model, with a fields: name, primary, accent, secondary, primary-fg. You can use Django Colorfield
  • Add a "Configuration" model (can be django-solo model but doesn't have to) with 1 dropdown field: theme
  • The selected theme in the configuration, should be loaded in the Django admin base_site.html template. This can be done with a Django context processor to load in the theme (for simplicity) and with some inline <style> to set the theme (see screenshots: :root)

We can accomplish this with minimal effort:

Theme settings:
image

Themes:
image
image

Added value

No response

Additional context

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.