Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Add everything
JoaRiski committed Oct 12, 2017
0 parents commit e1f8c0a
Showing 14 changed files with 955 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
*,cover
*.egg
*.egg-info/
*.log
*.manifest
*.pot
*.py[cod]
*.so
*.spec
.cache
.coverage
.coverage.*
.eggs/
.idea
.installed.cfg
.Python
.tox/
__pycache__/
build/
coverage.xml
develop-eggs/
dist/
docs/_build/
downloads/
eggs/
env/
htmlcov/
lib/
lib64/
nosetests.xml
parts/
pip-delete-this-directory.txt
pip-log.txt
sdist/
target/
var/
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include README.rst
include LICENSE

recursive-include django_sorting_field *.css *.html *.js
106 changes: 106 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
Django Sorting Field
====================

* This package implements a Django form field + widget for drag & drog sorting of items
* Sorting any item with a field called ``id`` is supported
* The drag and drop feature has been implemented with `html5sortable <https://lukasoppermann.github.io/html5sortable/index.html>`_.

Example of the widget
---------------------

.. image:: readme-media/example.gif

Usage
-----

The sort order field should be implemented on the model containing the sorted objects.
This allows ordering of different instances of the same item set differently.

Let's say you have image CarouselPlugin, Carousel, and Picture models, and you wish to be able to
sort the same Carousel instance differently on each CarouselPlugin.

You also have a CMSPlugin object for the carousel.

.. code-block:: python
class Carousel(models.Model):
pass
class Picture(models.Model):
carousel = models.ForeignKey(Carousel, related_name="pictures")
image = SomeImageField()
name = models.CharField()
class CarouselPlugin(CMSPlugin):
carousel = models.ForeignKey(Carousel, related_name="x")
class CMSCarouselPlugin(CMSPluginBase):
model = CarouselPlugin
def render(self, context, instance, placeholder):
context.update({
"pictures": self.instance.carousel.pictures.all(),
})
return context
Achieving the wanted behavior can be done in the following steps:

Add a (nullable) TextField to the model containing the order information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: python
class CarouselPlugin(CMSPlugin):
carousel = models.ForeignKey(Carousel, related_name="x")
carousel_order = models.TextField(null=True)
Add the SortingFormField to the CMS Plugin and populate it
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: python
from django_sorting_field.fields import SortingFormField
class CarouselPluginForm(forms.ModelForm):
carousel_order = SortingFormField()
def __init__(self, *args, **kwargs):
super(CarouselPluginForm, self).__init__(*args, **kwargs)
self.fields["carousel_order"].populate(
items=self.instance.carousel.pictures.all(),
)
class CMSCarouselPlugin(CMSPluginBase):
model = CarouselPlugin
form = CarouselPluginForm
def render(self, context, instance, placeholder):
context.update({
"pictures": self.instance.carousel.pictures.all(),
})
return context
Finally, sort the items passed to the context data
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: python
from django_sorting_field.utils import iterate_in_order
class CMSCarouselPlugin(CMSPluginBase):
model = CarouselPlugin
form = CarouselPluginForm
def render(self, context, instance, placeholder):
context.update({
"pictures": iterate_in_order(
self.instance.carousel.pictures.all(),
self.instance.carousel_order
),
})
return context
Empty file.
34 changes: 34 additions & 0 deletions django_sorting_field/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django import forms
import json

from widgets import SortingWidget
from utils import iterate_in_order, clean_order_json


class SortedItem(object):

def __init__(self, identifier, label):
self.id = identifier
self.label = label


class SortingFormField(forms.CharField):

def __init__(self, *args, **kwargs):
kwargs.update({
"widget": SortingWidget(),
"required": False,
})
self.items = ()
super(SortingFormField, self).__init__(*args, **kwargs)

def populate(self, items):
self.items = (SortedItem(item.pk, unicode(item)) for item in items)

def prepare_value(self, value):
value = clean_order_json(value)
return iterate_in_order(self.items, value)

def to_python(self, value):
value = clean_order_json(value)
return json.dumps(value)
19 changes: 19 additions & 0 deletions django_sorting_field/static/sorting/css/sorting_widget.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.sortable-widget-list {
}

.sortable-widget-list .sortable-widget-placeholder,
.sortable-widget-list .sortable-widget-item {
padding: 10px;
margin-bottom: 5px;
margin-top: 5px;
color: rgb(255, 255, 255);
background-color: #0bf;
border: 1px solid #0bf;
border-radius: 3px;
cursor: pointer;
}

.sortable-widget-list .sortable-widget-placeholder {
border: 1px dashed #0bf;
background-color: inherit;
}
2 changes: 2 additions & 0 deletions django_sorting_field/static/sorting/js/html.sortable.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions django_sorting_field/static/sorting/js/sorting_widget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const CreateSortableWidget = function(sortable_id) {
var sortable_list_id = "#" + sortable_id + "_list";

var refreshInputValue = function() {
result = [];
$(sortable_list_id).children(".sortable-widget-item").each(function(index, element) {
result.push($(element).data("id"));
});
$("input#" + sortable_id).val(JSON.stringify(result));
}

sortable(sortable_list_id, {
placeholder: '<div class="sortable-widget-placeholder">&nbsp;</div>'
})[0].addEventListener("sortstop", function() {
refreshInputValue();
});

refreshInputValue();
};
11 changes: 11 additions & 0 deletions django_sorting_field/templates/sorting/widgets/sorting_widget.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="sortable-widget-list" id="{{ id }}_list">
{% for item in items %}
<div class="sortable-widget-item" data-id="{{ item.id }}">{{ item.label }}</div>
{% endfor %}
</div>
<input type="hidden" id="{{ id }}" name="{{ name }}" value="">
<script type="text/javascript">
$(document).ready(function() {
CreateSortableWidget("{{ id }}");
});
</script>
28 changes: 28 additions & 0 deletions django_sorting_field/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import json


def clean_order_json(value):
value = "[]" if value is None else value

try:
return json.loads(value)
except ValueError:
return []


def iterate_in_order(items, order):
# In case our order is still in json format
if isinstance(order, basestring):
order = clean_order_json(order)

items_by_id = {item.id: item for item in items}

# Return items that are ordered first
for entry in order:
if entry not in items_by_id:
continue
yield items_by_id.pop(entry)

# Return the rest
for identifier, item in items_by_id.iteritems():
yield item
25 changes: 25 additions & 0 deletions django_sorting_field/widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from django.forms.widgets import Widget
from django.utils.safestring import mark_safe
from django.template.loader import render_to_string


class SortingWidget(Widget):
template_name = 'sorting/widgets/sorting_widget.html'

class Media:
css = {
"all": ("sorting/css/sorting_widget.css",)
}
js = (
"sorting/js/html.sortable.min.js",
"sorting/js/sorting_widget.js",
)

def render(self, name, value, attrs=None):
context = attrs
context.update({
"items": value,
"name": name,
})
html = render_to_string(self.template_name, context)
return mark_safe(html)
Binary file added readme-media/example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import setuptools

if __name__ == '__main__':
setuptools.setup(
name="django-sorting-field",
version="1.0.0",
description="Django Sorting Field",
packages=setuptools.find_packages(),
include_package_data=True,
)

0 comments on commit e1f8c0a

Please sign in to comment.