├── .gitignore ├── LICENCE.txt ├── README.md ├── build └── lib │ └── django_admin_relation_links │ ├── __init__.py │ └── models.py ├── django_admin_relation_links.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt └── top_level.txt ├── django_admin_relation_links ├── __init__.py ├── models.py └── options.py ├── screenshots ├── group-change-page.png ├── group-list-page.png ├── member-change-page.png └── member-list-page.png └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Admin relation links 2 | 3 | An easy way to add links to relations in the Django Admin site. 4 | 5 | 6 | ### Preview 7 | 8 | Imagine you have admin pages for 2 models: `Member` and `Group`. `Member` has a `ForeignKey` relation to `Group`. 9 | 10 | - When you look at a member, you want to easily navigate to its group. 11 | - When you look at a group, you want to easily navigate to a list of the members in that group. 12 | 13 | With the help of this app you can easily create these links: 14 | 15 | #### Member list page: 16 | ![Member list page](https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/master/screenshots/member-list-page.png) 17 | --------------------------- 18 | 19 | #### Member change page: 20 | ![Member change page](https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/master/screenshots/member-change-page.png) 21 | --------------------------- 22 | 23 | #### Group list page: 24 | ![Member list page](https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/master/screenshots/group-list-page.png) 25 | --------------------------- 26 | 27 | #### Group change page: 28 | ![Member change page](https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/master/screenshots/group-change-page.png) 29 | 30 | 31 | ### Install 32 | 33 | pip install django-admin-relation-links 34 | 35 | 36 | ### How to use 37 | 38 | The links are placed on the *change page* of the model and go to the *change 39 | list page* or the *change page* of the related model, depending on whether the 40 | related model has a `ForeignKey` to this model or this model has a `ForeignKey` 41 | to the related model, or if it's a `OneToOneField`. 42 | 43 | So for example, if you have these models: 44 | 45 | 46 | ```python 47 | from django.db import models 48 | 49 | 50 | class Group(models.Model): 51 | name = models.CharField(max_length=200) 52 | 53 | 54 | class Member(models.Model): 55 | name = models.CharField(max_length=200) 56 | group = models.ForeignKey(Group, related_name='members') 57 | ``` 58 | 59 | 60 | Then in the admin you can add links on the `Group` *change page* to the 61 | `Member` *change list page* (all the members of that group) and on the `Member` 62 | *change page* a link to the `Group` *change page* (the group of that member). 63 | Use the `changelist_links` and `change_links` fields: 64 | 65 | ```python 66 | from django.contrib import admin 67 | from django_admin_relation_links import AdminChangeLinksMixin 68 | 69 | 70 | @admin.register(Group) 71 | class GroupAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 72 | 73 | list_display = ['name'] 74 | 75 | # Use the `related_name` of the `Member.group` field. 76 | # If you have no `related_name` specified on your model, use the default 77 | # `related_name` assigned by Django. 78 | changelist_links = ['members'] 79 | 80 | @admin.register(Member) 81 | class MemberAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 82 | list_display = ['name'] 83 | 84 | # Here we just specify the name of the `ForeignKey` field. 85 | change_links = ['group'] 86 | ``` 87 | 88 | 89 | ### List page links 90 | 91 | It is possible to show links on admin *list page* as well, using the field `{field_name}_link`: 92 | 93 | ```python 94 | @admin.register(Member) 95 | class MemberAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 96 | list_display = ['name', 'group_link'] # Show link to group *change page* on member *list page* 97 | change_links = ['group'] 98 | ``` 99 | 100 | 101 | ### Link label 102 | 103 | By default, the label of the link is the string representation of the model 104 | instance. You can change the label by creating a method named 105 | `{field_name}_link_label()` like this: 106 | 107 | ```python 108 | def group_link_label(self, group): 109 | return '{} ({} members)'.format( 110 | group.name, 111 | group.members.count() 112 | ) 113 | ``` 114 | 115 | 116 | ### Extra options 117 | 118 | You can also set extra options like `label`, `model` and `lookup_filter` like this: 119 | 120 | ```python 121 | @admin.register(Group) 122 | class GroupAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 123 | list_display = ['name'] 124 | changelist_links = [ 125 | ('members', { 126 | 'label': 'All members', # Used as label for the link 127 | 'model': 'Member', # Specify a different model, you can also specify an app using `app.Member` 128 | 'lookup_filter': 'user_group' # Specify the GET parameter used for filtering the queryset 129 | }) 130 | ] 131 | ``` 132 | 133 | 134 | ### List page ordering 135 | 136 | When showing links on the list page, when you use that field for ordering, the 137 | default ordering field is the first field in the `ordering` option on the 138 | `Meta` class of the model of the related field. You can specify an alternative 139 | ordering like this: 140 | 141 | ```python 142 | @admin.register(Group) 143 | class MemberAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 144 | list_display = ['name', 'group_link'] 145 | change_links = [ 146 | ('group', { 147 | 'admin_order_field': 'group__name', # Allow to sort members by `group_link` column 148 | }) 149 | ] 150 | ``` 151 | -------------------------------------------------------------------------------- /build/lib/django_admin_relation_links/__init__.py: -------------------------------------------------------------------------------- 1 | from .options import AdminChangeLinksMixin 2 | 3 | 4 | __all__ = ['AdminChangeLinksMixin'] 5 | -------------------------------------------------------------------------------- /build/lib/django_admin_relation_links/models.py: -------------------------------------------------------------------------------- 1 | # This file needs to exist for Django to recognize this as a Django App 2 | -------------------------------------------------------------------------------- /django_admin_relation_links.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: django-admin-relation-links 3 | Version: 0.2.4 4 | Summary: An easy way to add links to relations in the Django Admin site. 5 | Home-page: https://github.com/gitaarik/django-admin-relation-links/ 6 | Author: gitaarik 7 | Author-email: gitaarik@posteo.net 8 | License: GNU Lesser General Public License v3 (LGPLv3) 9 | Description: # Django Admin relation links 10 | 11 | An easy way to add links to relations in the Django Admin site. 12 | 13 | 14 | ### Preview 15 | 16 | Imagine you have admin pages for 2 models: `Member` and `Group`. `Member` has a `ForeignKey` relation to `Group`. 17 | 18 | - When you look at a member, you want to easily navigate to its group. 19 | - When you look at a group, you want to easily navigate to a list of the members in that group. 20 | 21 | With the help of this app you can easily create these links: 22 | 23 | #### Member list page: 24 | ![Member list page](https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/master/screenshots/member-list-page.png) 25 | --------------------------- 26 | 27 | #### Member change page: 28 | ![Member change page](https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/master/screenshots/member-change-page.png) 29 | --------------------------- 30 | 31 | #### Group list page: 32 | ![Member list page](https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/master/screenshots/group-list-page.png) 33 | --------------------------- 34 | 35 | #### Group change page: 36 | ![Member change page](https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/master/screenshots/group-change-page.png) 37 | 38 | 39 | ### Install 40 | 41 | pip install django-admin-relation-links 42 | 43 | 44 | ### How to use 45 | 46 | The links are placed on the *change page* of the model and go to the *change 47 | list page* or the *change page* of the related model, depending on whether the 48 | related model has a `ForeignKey` to this model or this model has a `ForeignKey` 49 | to the related model, or if it's a `OneToOneField`. 50 | 51 | So for example, if you have these models: 52 | 53 | 54 | ```python 55 | from django.db import models 56 | 57 | 58 | class Group(models.Model): 59 | name = models.CharField(max_length=200) 60 | 61 | 62 | class Member(models.Model): 63 | name = models.CharField(max_length=200) 64 | group = models.ForeignKey(Group, related_name='members') 65 | ``` 66 | 67 | 68 | Then in the admin you can add links on the `Group` *change page* to the 69 | `Member` *change list page* (all the members of that group) and on the `Member` 70 | *change page* a link to the `Group` *change page* (the group of that member). 71 | 72 | ```python 73 | from django.contrib import admin 74 | from django_admin_relation_links import AdminChangeLinksMixin 75 | 76 | 77 | @admin.register(Group) 78 | class GroupAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 79 | list_display = ['name'] 80 | changelist_links = ['members'] # Use the `related_name` of the `Member.group` field 81 | 82 | 83 | @admin.register(Member) 84 | class MemberAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 85 | list_display = ['name'] 86 | change_links = ['group'] # Just specify the name of the `ForeignKey` field 87 | ``` 88 | 89 | 90 | ### List page links 91 | 92 | It is possible to show links on admin *list page* as well, using the field `{field_name}_link`: 93 | 94 | ```python 95 | @admin.register(Member) 96 | class MemberAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 97 | list_display = ['name', 'group_link'] # Show link to group *change page* on member *list page* 98 | change_links = ['group'] 99 | ``` 100 | 101 | 102 | ### Link label 103 | 104 | By default, the label of the link is the string representation of the model 105 | instance. You can change the label by creating a method named 106 | `{field_name}_link_label()` like this: 107 | 108 | ```python 109 | def group_link_label(self, group): 110 | return '{} ({} members)'.format( 111 | group.name, 112 | group.members.count() 113 | ) 114 | ``` 115 | 116 | 117 | ### Extra options 118 | 119 | You can also set extra options like `label`, `model` and `lookup_filter` like this: 120 | 121 | ```python 122 | @admin.register(Group) 123 | class GroupAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 124 | list_display = ['name'] 125 | changelist_links = [ 126 | ('members', { 127 | 'label': 'All members', # Used as label for the link 128 | 'model': 'Member', # Specify a different model, you can also specify an app using `app.Member` 129 | 'lookup_filter': 'user_group' # Specify the GET parameter used for filtering the queryset 130 | }) 131 | ] 132 | ``` 133 | 134 | 135 | ### List page ordering 136 | 137 | When showing links on the list page, when you use that field for ordering, the 138 | default ordering field is the first field in the `ordering` option on the 139 | `Meta` class of the model of the related field. You can specify an alternative 140 | ordering like this: 141 | 142 | ```python 143 | @admin.register(Group) 144 | class MemberAdmin(AdminChangeLinksMixin, admin.ModelAdmin): 145 | list_display = ['name', 'group_link'] 146 | change_links = [ 147 | ('group', { 148 | 'admin_order_field': 'group__name', # Allow to sort members by `group_link` column 149 | }) 150 | ] 151 | ``` 152 | 153 | Keywords: django admin relation foreignkey link 154 | Platform: UNKNOWN 155 | Classifier: Development Status :: 4 - Beta 156 | Classifier: Environment :: Web Environment 157 | Classifier: Intended Audience :: Developers 158 | Classifier: Topic :: Software Development :: Libraries 159 | Classifier: Programming Language :: Python :: 3 160 | Classifier: Framework :: Django 161 | Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) 162 | Classifier: Operating System :: OS Independent 163 | Requires-Python: >=3 164 | Description-Content-Type: text/markdown 165 | -------------------------------------------------------------------------------- /django_admin_relation_links.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | django_admin_relation_links/__init__.py 4 | django_admin_relation_links/models.py 5 | django_admin_relation_links/options.py 6 | django_admin_relation_links.egg-info/PKG-INFO 7 | django_admin_relation_links.egg-info/SOURCES.txt 8 | django_admin_relation_links.egg-info/dependency_links.txt 9 | django_admin_relation_links.egg-info/top_level.txt -------------------------------------------------------------------------------- /django_admin_relation_links.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /django_admin_relation_links.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | django_admin_relation_links 2 | -------------------------------------------------------------------------------- /django_admin_relation_links/__init__.py: -------------------------------------------------------------------------------- 1 | from .options import AdminChangeLinksMixin 2 | 3 | 4 | __all__ = ['AdminChangeLinksMixin'] 5 | -------------------------------------------------------------------------------- /django_admin_relation_links/models.py: -------------------------------------------------------------------------------- 1 | # This file needs to exist for Django to recognize this as a Django App 2 | -------------------------------------------------------------------------------- /django_admin_relation_links/options.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from django.utils.html import format_html 3 | 4 | 5 | def parse_field_config(links_config): 6 | 7 | for link in links_config: 8 | 9 | if isinstance(link, (tuple, list)): 10 | model_field_name, options = (link[0], link[1]) 11 | else: 12 | model_field_name, options = (link, {}) 13 | 14 | admin_field_name = '{}_link'.format(model_field_name) 15 | 16 | yield model_field_name, admin_field_name, options 17 | 18 | 19 | def underscore_to_capitalize(string): 20 | return string.replace('_', ' ').capitalize() 21 | 22 | 23 | def get_link_field(url, label): 24 | return format_html('{}', url, label) 25 | 26 | 27 | class AdminChangeLinksMixin(): 28 | 29 | change_links = [] 30 | changelist_links = [] 31 | 32 | def __init__(self, *args, **kwargs): 33 | super(AdminChangeLinksMixin, self).__init__(*args, **kwargs) 34 | self._add_change_link_fields() 35 | self._add_changelist_link_fields() 36 | 37 | def _add_change_link_fields(self): 38 | for model_field_name, admin_field_name, options in parse_field_config(self.change_links): 39 | self._add_change_link(model_field_name, admin_field_name, options) 40 | 41 | def _add_change_link(self, model_field_name, admin_field_name, options): 42 | 43 | def make_change_link(model_field_name, options): 44 | def func(instance): 45 | return self._get_change_link(instance, model_field_name, admin_field_name, options) 46 | self.decorate_link_func(func, model_field_name, options) 47 | return func 48 | 49 | self._add_admin_field(admin_field_name, make_change_link(model_field_name, options)) 50 | 51 | def _get_change_link(self, instance, model_field_name, admin_field_name, options): 52 | 53 | target_instance = getattr(instance, model_field_name) 54 | 55 | if not target_instance: 56 | return 57 | 58 | return get_link_field( 59 | reverse( 60 | '{}:{}_{}_change'.format( 61 | self.admin_site.name, 62 | options.get('app') or target_instance._meta.app_label, 63 | options.get('model') or target_instance._meta.model_name 64 | ), 65 | args=[target_instance.pk] 66 | ), 67 | self.link_label(admin_field_name, target_instance) 68 | ) 69 | 70 | def link_label(self, admin_field_name, target_instance): 71 | 72 | label_method_name = '{}_label'.format(admin_field_name) 73 | 74 | if hasattr(self, label_method_name): 75 | return getattr(self, label_method_name)(target_instance) 76 | 77 | return str(target_instance) 78 | 79 | def _add_changelist_link_fields(self): 80 | for model_field_name, admin_field_name, options in parse_field_config(self.changelist_links): 81 | self._add_changelist_link(model_field_name, admin_field_name, options) 82 | 83 | def _add_changelist_link(self, model_field_name, admin_field_name, options): 84 | 85 | def make_changelist_link(model_field_name, options): 86 | def func(instance): 87 | return self._get_changelist_link(instance, model_field_name, options) 88 | self.decorate_link_func(func, model_field_name, options) 89 | return func 90 | 91 | self._add_admin_field(admin_field_name, make_changelist_link(model_field_name, options)) 92 | 93 | def _get_changelist_link(self, instance, model_field_name, options): 94 | 95 | def get_url(): 96 | return reverse( 97 | '{}:{}_{}_changelist'.format( 98 | self.admin_site.name, 99 | *self._get_app_model(instance, model_field_name, options) 100 | ) 101 | ) 102 | 103 | def get_lookup_filter(): 104 | return options.get('lookup_filter') or instance._meta.get_field(model_field_name).field.name 105 | 106 | def get_label(): 107 | return ( 108 | options.get('label') 109 | or getattr(instance, model_field_name).model._meta.verbose_name_plural.capitalize() 110 | ) 111 | 112 | return get_link_field( 113 | '{}?{}={}'.format(get_url(), get_lookup_filter(), instance.pk), 114 | get_label() 115 | ) 116 | 117 | def _get_app_model(self, instance, model_field_name, options): 118 | 119 | options_model = options.get('model') 120 | 121 | if options_model: 122 | if '.' in options_model: 123 | app, model = options_model.lower().split('.') 124 | else: 125 | app = self.opts.app_label 126 | model = options_model.lower() 127 | else: 128 | model_meta = getattr(instance, model_field_name).model._meta 129 | app = model_meta.app_label 130 | model = model_meta.model_name 131 | 132 | return app, model 133 | 134 | def decorate_link_func(self, func, model_field_name, options): 135 | 136 | func.short_description = options.get('label') or underscore_to_capitalize(model_field_name) 137 | 138 | if options.get('admin_order_field'): 139 | func.admin_order_field = options['admin_order_field'] 140 | else: 141 | try: 142 | field = self.model._meta.get_field(model_field_name) 143 | except: 144 | pass 145 | else: 146 | if ( 147 | hasattr(field.related_model._meta, 'ordering') 148 | and len(field.related_model._meta.ordering) > 0 149 | ): 150 | func.admin_order_field = '{}__{}'.format( 151 | field.name, 152 | field.related_model._meta.ordering[0].replace('-', '') 153 | ) 154 | 155 | def _add_admin_field(self, field_name, func): 156 | 157 | if not hasattr(self, field_name): 158 | setattr(self, field_name, func) 159 | 160 | self._add_field_to_fields(field_name) 161 | self._add_field_to_readonly_fields(field_name) 162 | 163 | def _add_field_to_fields(self, field_name): 164 | if self.fields and field_name not in self.fields: 165 | self.fields = list(self.fields) + [field_name] 166 | 167 | def _add_field_to_readonly_fields(self, field_name): 168 | 169 | if not self.readonly_fields: 170 | self.readonly_fields = [] 171 | 172 | if field_name not in self.readonly_fields: 173 | self.readonly_fields = list(self.readonly_fields) + [field_name] 174 | -------------------------------------------------------------------------------- /screenshots/group-change-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/286c59151c98902b29d6d600271b228c615bfdb6/screenshots/group-change-page.png -------------------------------------------------------------------------------- /screenshots/group-list-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/286c59151c98902b29d6d600271b228c615bfdb6/screenshots/group-list-page.png -------------------------------------------------------------------------------- /screenshots/member-change-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/286c59151c98902b29d6d600271b228c615bfdb6/screenshots/member-change-page.png -------------------------------------------------------------------------------- /screenshots/member-list-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitaarik/django-admin-relation-links/286c59151c98902b29d6d600271b228c615bfdb6/screenshots/member-list-page.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | with open('README.md', 'r') as fh: 5 | long_description = fh.read() 6 | 7 | setup( 8 | name='django-admin-relation-links', 9 | keywords='django admin relation foreignkey link', 10 | version='0.2.4', 11 | author='gitaarik', 12 | author_email='gitaarik@posteo.net', 13 | packages=['django_admin_relation_links'], 14 | url='https://github.com/gitaarik/django-admin-relation-links/', 15 | license='GNU Lesser General Public License v3 (LGPLv3)', 16 | description='An easy way to add links to relations in the Django Admin site.', 17 | long_description=long_description, 18 | long_description_content_type='text/markdown', 19 | classifiers=[ 20 | 'Development Status :: 4 - Beta', 21 | 'Environment :: Web Environment', 22 | 'Intended Audience :: Developers', 23 | 'Topic :: Software Development :: Libraries', 24 | 'Programming Language :: Python :: 3', 25 | 'Framework :: Django', 26 | 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', 27 | 'Operating System :: OS Independent' 28 | ], 29 | python_requires='>=3' 30 | ) 31 | --------------------------------------------------------------------------------