Welcome to Django Opt-out application’s documentation!

Contents:

Django Opt-out application

https://img.shields.io/pypi/v/django-opt-out.svg https://img.shields.io/travis/wooyek/django-opt-out.svg Documentation Status Coveralls.io coverage CodeCov coverage Maintainability License Tweet about this project https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg

Allow everybody to unsubscribe your messages, user accounts are not required.

Features

  • A single page form for opt-out feedback submission
  • Feedback options text controlled from django admin
  • Predefined feedback defaults available from django manage command
  • Feedback translations done in django admin
  • Feedback options selection based on tags supplied to the opt-out url
  • Ability to preselect a feedback option
  • Ability to change selected feedback options after submission
  • Ability to set tag:value pair on opt-out url and store them on submission with user feedback
  • Signal to modify opt-out form before rendering
  • Signal on opt-out feedback submission
  • Easily overridable thank you / goodbye view
  • Opt-out form with a easily overridable base template

Demo

To run an example project for this django reusable app, click the button below and start a demo serwer on Heroku

Deploy Django Opt-out example project to Heroku Deploy Django Opt-out example project to Heroku

Quickstart

Install Django Opt-out application:

pip install django-opt-out

Add it to your INSTALLED_APPS:

INSTALLED_APPS = (
    ...
    'django_opt_out.apps.DjangoOptOutConfig',
    ...
)

Add Django Opt-out application’s URL patterns:

from django_opt_out import urls as django_opt_out_urls


urlpatterns = [
    ...
    url(r'^', include(django_opt_out_urls)),
    ...
]

Add unsubscribe links to your emails:

from django_opt_out.utils import get_opt_out_path
email='Django Opt-out <django-opt-out@niepodam.pl>'
unsubscribe = get_opt_out_path(email, 'some', 'tags', 'controlling', 'questionnaire')

# unsubscribe link will not have a domain name and scheme
# you can build prefix from request, but I prefer to set it in settings
from django.conf import settings
unsubscribe = settings.BASE_URL + unsubscribe
body = 'Hello, Regards\n\nUnsubscribe: ' + unsubscribe

from django.core import mail
message = mail.EmailMultiAlternatives(body=body, to=[email])
message.extra_headers['List-Unsubscribe'] = "<{}>".format(unsubscribe)
message.send()

Running Tests

Does the code actually work?

source <YOURVIRTUALENV>/bin/activate
(myenv) $ pip install tox
(myenv) $ tox

Credits

This package was created with Cookiecutter and the wooyek/cookiecutter-django-app project template.

Installation

Stable release

To install Django Opt-out application, run this command in your terminal:

$ pip install django-opt-out

This is the preferred method to install Django Opt-out application, as it will always install the most recent stable release.

If you don’t have pip installed, this Python installation guide can guide you through the process.

From sources

You can either clone the public repository:

$ git clone git://github.com/wooyek/django-opt-out

Or download the download source from project website. Once you have a copy of the source, you can install it with:

$ python setup.py install

Usage

To use Django Opt-out application in a project, add it to your INSTALLED_APPS:

INSTALLED_APPS = (
    ...
    'django_opt_out.apps.DjangoOptOutConfig',
    ...
)

Add Django Opt-out application’s URL patterns:

from django_opt_out import urls as django_opt_out_urls


urlpatterns = [
    ...
    url(r'^', include(django_opt_out_urls)),
    ...
]

Api docs

django_opt_out package

Subpackages

django_opt_out.management package
Subpackages
django_opt_out.management.commands package
Submodules
django_opt_out.management.commands.opt_out_feedback_defaults module
class django_opt_out.management.commands.opt_out_feedback_defaults.Command(stdout=None, stderr=None, no_color=False)[source]

Bases: django.core.management.base.BaseCommand

add_arguments(parser)[source]

Entry point for subclassed commands to add custom arguments.

data_files = (('tags.csv', <class 'django_opt_out.resources.OptOutTagResource'>), ('feedback.csv', <class 'django_opt_out.resources.OptOutFeedbackResource'>), ('feedback-translations.csv', <class 'django_opt_out.resources.OptOutFeedbackTranslationResource'>))
handle(*args, **options)[source]

The actual logic of the command. Subclasses must implement this method.

help = 'Imports default opt-out feedback options to empty database'
import_all()[source]
static import_data_file(name, resource)[source]
Module contents
Module contents
django_opt_out.plugins package
Subpackages
django_opt_out.plugins.sparkpost package
Submodules
django_opt_out.plugins.sparkpost.apps module
class django_opt_out.plugins.sparkpost.apps.DjangoOptOutSparkPostConfig(app_name, app_module)[source]

Bases: django.apps.config.AppConfig

name = 'django_opt_out.plugins.sparkpost'
ready()[source]

Override this method in subclasses to run code when Django starts.

verbose_name = 'Messaging Opt-Outs SparkPost plugin'
django_opt_out.plugins.sparkpost.hooks module
django_opt_out.plugins.sparkpost.hooks.create_opt_out(sender, request, email, data, **kwargs)[source]
django_opt_out.plugins.sparkpost.hooks.get_client(setting='SPARKPOST_API_KEY')[source]
django_opt_out.plugins.sparkpost.hooks.remove_suppression(sender, view, request, opt_out, **kwargs)[source]
django_opt_out.plugins.sparkpost.hooks.suppress_email(sender, view, request, opt_out, **kwargs)[source]
django_opt_out.plugins.sparkpost.signals module
django_opt_out.plugins.sparkpost.urls module
django_opt_out.plugins.sparkpost.views module
class django_opt_out.plugins.sparkpost.views.SparkPostUnsubscribeWebhook(**kwargs)[source]

Bases: django.views.generic.base.View

dispatch(*args, **kwargs)[source]
post(request, *args, **kwargs)[source]
process_data(data)[source]
Module contents
django_opt_out.plugins.sparkpost.send_email(subject, to, ctx, template_html, template_txt=None, **kwargs)[source]

Send rendered message with SparkPost unsubscribe support

Module contents

Submodules

django_opt_out.admin module

class django_opt_out.admin.OptOutAdmin(model, admin_site)[source]

Bases: import_export.admin.ImportExportMixin, django.contrib.admin.options.ModelAdmin

date_hierarchy = 'ts'
list_display = ('ts', 'email', 'ip', 'host', 'ua')
list_filter = ('ip', 'host', 'ua', 'feedback', 'tags')
media
readonly_fields = ('email', 'ts', 'comment', 'ip', 'host', 'ua', 'cookies', 'data', 'ssl')
resource_class

alias of django_opt_out.resources.OptOutResource

class django_opt_out.admin.OptOutFeedbackAdmin(model, admin_site)[source]

Bases: import_export.admin.ImportExportMixin, django.contrib.admin.options.ModelAdmin

all_tag_names(obj)[source]
inlines = [<class 'django_opt_out.admin.OptOutFeedbackTranslationInline'>]
list_display = ('text', 'slug', 'default', 'ordinal', 'all_tag_names')
list_filter = ('tags', 'default')
media
queryset(request, queryset)[source]
resource_class

alias of django_opt_out.resources.OptOutFeedbackResource

class django_opt_out.admin.OptOutFeedbackTranslationAdmin(model, admin_site)[source]

Bases: import_export.admin.ImportExportMixin, django.contrib.admin.options.ModelAdmin

list_display = ('text', 'language', 'feedback')
media
resource_class

alias of django_opt_out.resources.OptOutFeedbackTranslationResource

class django_opt_out.admin.OptOutFeedbackTranslationInline(parent_model, admin_site)[source]

Bases: django.contrib.admin.options.TabularInline

media
model

alias of django_opt_out.models.OptOutFeedbackTranslation

resource_class

alias of django_opt_out.resources.OptOutFeedbackResource

class django_opt_out.admin.OptOutTagAdmin(model, admin_site)[source]

Bases: import_export.admin.ImportExportMixin, django.contrib.admin.options.ModelAdmin

list_display = ('name', 'description')
media
resource_class

alias of django_opt_out.resources.OptOutTagResource

django_opt_out.app_settings module

django_opt_out.apps module

class django_opt_out.apps.DjangoOptOutConfig(app_name, app_module)[source]

Bases: django.apps.config.AppConfig

name = 'django_opt_out'
ready()[source]

Override this method in subclasses to run code when Django starts.

verbose_name = 'Messaging Opt-Outs'
django_opt_out.apps.setup_app_settings()[source]

django_opt_out.cli module

Console script for django-opt-out.

django_opt_out.factories module

class django_opt_out.factories.OptOutFactory[source]

Bases: factory.django.DjangoModelFactory

email = <factory.faker.Faker object>
ts = <factory.declarations.LazyFunction object>
class django_opt_out.factories.OptOutFeedbackFactory[source]

Bases: factory.django.DjangoModelFactory

tags = <factory.declarations.PostGeneration object>
text = <factory.faker.Faker object>
class django_opt_out.factories.OptOutFeedbackTranslationFactory[source]

Bases: factory.django.DjangoModelFactory

feedback = <factory.declarations.SubFactory object>
language = 'pl'
text = <factory.faker.Faker object>
class django_opt_out.factories.OptOutTagFactory[source]

Bases: factory.django.DjangoModelFactory

name = <factory.faker.Faker object>
class django_opt_out.factories.OptOutTagValueFactory[source]

Bases: factory.django.DjangoModelFactory

opt_out = <factory.declarations.SubFactory object>
tag = <factory.declarations.SubFactory object>
class django_opt_out.factories.UserFactory[source]

Bases: factory.django.DjangoModelFactory

email = <factory.faker.Faker object>
first_name = <factory.faker.Faker object>
is_active = True
is_staff = False
last_name = <factory.faker.Faker object>
username = <factory.declarations.Sequence object>

django_opt_out.forms module

class django_opt_out.forms.OptOutForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None)[source]

Bases: django.forms.models.ModelForm

class Meta[source]

Bases: object

fields = ('email', 'feedback', 'comment')
labels = {'comment': 'Please tell us what can we do better'}
model

alias of django_opt_out.models.OptOut

base_fields = {'comment': <django.forms.fields.CharField object at 0x7fbb2bff9400>, 'email': <django.forms.fields.EmailField object at 0x7fbb2bff92b0>, 'feedback': <django_opt_out.forms.TranslatedMultipleChoiceField object at 0x7fbb2bff0f98>}
declared_fields = {'feedback': <django_opt_out.forms.TranslatedMultipleChoiceField object at 0x7fbb2bff0f98>}
media
required_css_class = 'required'
save(commit=True)[source]

Save this form’s self.instance object if commit=True. Otherwise, add a save_m2m() method to the form which can be called after the instance is saved manually at a later time. Return the model instance.

class django_opt_out.forms.TranslatedMultipleChoiceField(queryset, **kwargs)[source]

Bases: django.forms.models.ModelMultipleChoiceField

label_from_instance(obj)[source]

Convert objects into strings and generate the labels for the choices presented by this object. Subclasses can override this method to customize the display of the choices.

django_opt_out.models module

class django_opt_out.models.OptOut(id, email, ts, confirmed, data, comment, secret, ssl, host, ip, ua, cookies)[source]

Bases: django_powerbank.db.models.base.BaseModel

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

comment

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

confirmed

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

cookies

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

data

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

email

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

feedback

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

Pizza.toppings and Topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

get_next_by_ts(*, field=<django.db.models.fields.DateTimeField: ts>, is_next=True, **kwargs)
get_previous_by_ts(*, field=<django.db.models.fields.DateTimeField: ts>, is_next=False, **kwargs)
host

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

ip

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

objects = <django.db.models.manager.Manager object>
save(force_insert=False, force_update=False, using=None, update_fields=None)[source]

Save the current instance. Override this in a subclass if you want to control the saving process.

The ‘force_insert’ and ‘force_update’ parameters can be used to insist that the “save” must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.

secret

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

ssl

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

tags

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

ts

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

ua

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class django_opt_out.models.OptOutFeedback(id, text, slug, default, ordinal)[source]

Bases: django_powerbank.db.models.base.BaseModel

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

default

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

objects = <django.db.models.manager.Manager object>
ordinal

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

out_outs

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

Pizza.toppings and Topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

slug

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

tags

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

Pizza.toppings and Topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

text

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

trans(language=None)[source]
translations

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

class django_opt_out.models.OptOutFeedbackTranslation(id, feedback, language, text)[source]

Bases: django_powerbank.db.models.base.BaseModel

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

feedback

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

feedback_id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

get_language_display(*, field=<django.db.models.fields.CharField: language>)
id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

language

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

objects = <django.db.models.manager.Manager object>
text

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class django_opt_out.models.OptOutTag(id, name, description)[source]

Bases: django_powerbank.db.models.base.BaseModel

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

description

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

feedback

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

Pizza.toppings and Topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

name

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

objects = <django.db.models.manager.Manager object>
tags

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

class django_opt_out.models.OptOutTagValue(id, opt_out, tag, value)[source]

Bases: django_powerbank.db.models.base.BaseModel

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

objects = <django.db.models.manager.Manager object>
opt_out

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

opt_out_id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

tag

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

tag_id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

value

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

django_opt_out.resources module

class django_opt_out.resources.OptOutFeedbackResource[source]

Bases: import_export.resources.ModelResource

class Meta[source]

Bases: object

export_order = ('text',)
fields = ('id', 'text', 'slug', 'default', 'ordinal', 'tags')
model

alias of django_opt_out.models.OptOutFeedback

fields = {'default': <import_export.fields.Field: default>, 'id': <import_export.fields.Field: id>, 'ordinal': <import_export.fields.Field: ordinal>, 'slug': <import_export.fields.Field: slug>, 'tags': <import_export.fields.Field: tags>, 'text': <import_export.fields.Field: text>}
class django_opt_out.resources.OptOutFeedbackTranslationResource[source]

Bases: import_export.resources.ModelResource

class Meta[source]

Bases: object

export_order = ('text',)
fields = ('id', 'feedback', 'text', 'language')
model

alias of django_opt_out.models.OptOutFeedbackTranslation

fields = {'feedback': <import_export.fields.Field: feedback>, 'id': <import_export.fields.Field: id>, 'language': <import_export.fields.Field: language>, 'text': <import_export.fields.Field: text>}
class django_opt_out.resources.OptOutResource[source]

Bases: import_export.resources.ModelResource

class Meta[source]

Bases: object

export_order = ('ts',)
fields = ('email', 'ts', 'confirmed', 'data', 'comment', 'feedback', 'secret', 'ssl', 'ip', 'ua', 'cookies')
model

alias of django_opt_out.models.OptOut

fields = {'comment': <import_export.fields.Field: comment>, 'confirmed': <import_export.fields.Field: confirmed>, 'cookies': <import_export.fields.Field: cookies>, 'data': <import_export.fields.Field: data>, 'email': <import_export.fields.Field: email>, 'feedback': <import_export.fields.Field: tags>, 'ip': <import_export.fields.Field: ip>, 'secret': <import_export.fields.Field: secret>, 'ssl': <import_export.fields.Field: ssl>, 'ts': <import_export.fields.Field: ts>, 'ua': <import_export.fields.Field: ua>}
class django_opt_out.resources.OptOutTagResource[source]

Bases: import_export.resources.ModelResource

class Meta[source]

Bases: object

export_order = ('name',)
fields = ('id', 'name')
model

alias of django_opt_out.models.OptOutTag

fields = {'id': <import_export.fields.Field: id>, 'name': <import_export.fields.Field: name>}
class django_opt_out.resources.OptOutTagValueResource[source]

Bases: import_export.resources.ModelResource

class Meta[source]

Bases: object

export_order = ('text',)
fields = ('opt_out', 'tag', 'value')
model

alias of django_opt_out.models.OptOutTagValue

fields = {'opt_out': <import_export.fields.Field: tags>, 'tag': <import_export.fields.Field: tags>, 'value': <import_export.fields.Field: value>}

django_opt_out.signals module

django_opt_out.signals.send_signal(signal, *args, **kwargs)[source]

Log eny errors returned in the signal call responses

django_opt_out.urls module

django_opt_out.utils module

django_opt_out.utils.get_opt_out_path(email, *tags)[source]
django_opt_out.utils.get_opt_out_url(email, base_url=None, *tags)[source]
django_opt_out.utils.get_password(email)[source]
django_opt_out.utils.validate_password(email, encoded)[source]

django_opt_out.views module

class django_opt_out.views.OptOutBase(**kwargs)[source]

Bases: django_powerbank.views.auth.AbstractAccessView

check_authorization(*args, **kwargs)[source]
class django_opt_out.views.OptOutConfirm(**kwargs)[source]

Bases: pascal_templates.views.CreateView

email_confirmed()[source]
form_class

alias of django_opt_out.forms.OptOutForm

form_valid(form)[source]

If the form is valid, save the associated model.

get_context_data(**kwargs)[source]

Insert the form into the context dict.

get_form(form_class=None)[source]

Return an instance of the form to be used in this view.

get_initial()[source]

Return the initial data to use for forms on this view.

get_success_url()[source]

Return the URL to redirect to after processing a valid form.

get_tags()[source]
model

alias of django_opt_out.models.OptOut

save_tags()[source]
template_name = 'django_opt_out/OptOut/form.html'
class django_opt_out.views.OptOutRemoved(**kwargs)[source]

Bases: django.views.generic.base.TemplateView

template_name = 'django_opt_out/OptOut/removed.html'
class django_opt_out.views.OptOutSuccess(**kwargs)[source]

Bases: django_opt_out.views.OptOutBase, pascal_templates.views.DetailView

model

alias of django_opt_out.models.OptOut

post(request, *args, **kwargs)[source]
template_name = 'django_opt_out/OptOut/success.html'
class django_opt_out.views.OptOutUpdate(**kwargs)[source]

Bases: django_opt_out.views.OptOutBase, pascal_templates.views.UpdateView

form_class

alias of django_opt_out.forms.OptOutForm

get_form(form_class=None)[source]

Return an instance of the form to be used in this view.

get_success_url()[source]

Return the URL to redirect to after processing a valid form.

model

alias of django_opt_out.models.OptOut

template_name = 'django_opt_out/OptOut/form.html'

Module contents

Top-level package for Django Opt-out application.

Contributing

Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.

You can contribute in many ways:

Types of Contributions

Report Bugs

Report bugs at https://github.com/wooyek/django-opt-out/issues.

If you are reporting a bug, please include:

  • Your operating system name and version.
  • Any details about your local setup that might be helpful in troubleshooting.
  • Detailed steps to reproduce the bug.

Fix Bugs

Look through the GitHub issues for bugs. Anything tagged with “bug” and “help wanted” is open to whoever wants to implement it.

Implement Features

Look through the GitHub issues for features. Anything tagged with “enhancement” and “help wanted” is open to whoever wants to implement it.

Write Documentation

Django Opt-out application could always use more documentation, whether as part of the official Django Opt-out application docs, in docstrings, or even on the web in blog posts, articles, and such.

Submit Feedback

The best way to send feedback is to file an issue at https://github.com/wooyek/django-opt-out/issues.

If you are proposing a feature:

  • Explain in detail how it would work.
  • Keep the scope as narrow as possible, to make it easier to implement.
  • Remember that this is a volunteer-driven project, and that contributions are welcome :)

Get Started!

Ready to contribute? Here’s how to set up django-opt-out for local development.

  1. Fork the django-opt-out repo on github.com

  2. Clone your fork locally:

    $ git clone git@github.com:your_name_here/django-opt-out.git
    
  3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:

    $ mkvirtualenv django-opt-out
    $ cd django-opt-out/
    $ python setup.py develop
    
  4. Create a branch for local development:

    $ git checkout -b name-of-your-bugfix-or-feature
    

    Now you can make your changes locally.

  5. When you’re done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:

    $ flake8 django-opt-out tests
    $ tox -e check
    $ python setup.py test or py.test
    $ tox
    

    To get flake8 and tox, just pip install them into your virtualenv.

  6. Commit your changes and push your branch to GitHub:

    $ git add .
    $ git commit -m "Your detailed description of your changes."
    $ git push origin name-of-your-bugfix-or-feature
    
  7. Submit a pull request through the GitHub website.

Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

  1. The pull request should include tests.
  2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst.
  3. The pull request should work for Python versions mentioned in tox.ini file. Check https://travis-ci.org/wooyek/django-opt-out/pull_requests and make sure that the tests pass for all supported Python versions.

Tips

To run a subset of tests:

$ pytest tests.test_models

Credits

Development Lead

Contributors

None yet. Why not be the first?

History

0.1.0 (2017-11-03)

  • First release on PyPI.

Indices and tables