Add initial basic admin import/export support

This commit is contained in:
Christopher C. Wells 2020-02-16 15:58:49 -08:00 committed by Christopher Charbonneau Wells
parent a751b0b2e0
commit 014b373a78
13 changed files with 375 additions and 24 deletions

View File

@ -1,11 +1,8 @@
[[source]] [[source]]
verify_ssl = true verify_ssl = true
url = "https://pypi.python.org/simple" url = "https://pypi.python.org/simple"
[packages] [packages]
django = "*" django = "*"
djangorestframework = "*" djangorestframework = "*"
django-filter = "*" django-filter = "*"
@ -21,10 +18,9 @@ easy-thumbnails = "*"
python-dotenv = "*" python-dotenv = "*"
django-storages = "*" django-storages = "*"
boto3 = "*" boto3 = "*"
django-import-export = "*"
[dev-packages] [dev-packages]
coveralls = "*" coveralls = "*"
flake8 = "*" flake8 = "*"
ipaddress = "*" ipaddress = "*"

109
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "21a6001d830ce191858fed3bd90d35af9317737dddfcb41a3dd1e714f8e8fd54" "sha256": "60e32aea6660fd1030af59723675270b96317b565230d48f7262f158f82c2ee3"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@ -22,17 +22,30 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:09eccb6cd41381c4ff1d626c3a19884b5b1f1424d15a96004d077b532ef393d1", "sha256:33462a79d57c9c4a215e075472509537d03545f54566fc4f776fb0f4cfa616f6",
"sha256:664be6e0e20cb064dda4ac3397082e3dcc453abb8b2bd2cf64066677e0fb2266" "sha256:34f9a04f529dc849f0e427782d6f3c6b62f7fb734d8f4859b17e5dee0855323e"
], ],
"version": "==1.11.13" "version": "==1.12.0"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:6478d9207db6dbcb5106fd4db2cdd5194d0b2dc0b73776019d56877ab802fe87", "sha256:055da4826f6c9158e4a61549d57a2ce449c27d44ce34ab4c96c7bb7b5c993efc",
"sha256:6ffb78b331b0954cfe5c51958cb51522ab0e2999442422949b080a3e1bc76ee1" "sha256:1f7cecfcd38c7cac17b5386014eb04626d1c7559ee8d8ec1526058cd23f6d1d4"
], ],
"version": "==1.14.13" "version": "==1.15.0"
},
"defusedxml": {
"hashes": [
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
],
"version": "==0.6.0"
},
"diff-match-patch": {
"hashes": [
"sha256:a809a996d0f09b9bbd59e9bbd0b71eed8c807922512910e05cbd3f9480712ddb"
],
"version": "==20181111"
}, },
"dj-database-url": { "dj-database-url": {
"hashes": [ "hashes": [
@ -55,6 +68,13 @@
], ],
"version": "==2.2.0" "version": "==2.2.0"
}, },
"django-import-export": {
"hashes": [
"sha256:939109c8be31a8bcc8779634c080c4510669aba576b173f37cfdf838a705d0ac",
"sha256:94c2030ba1b141bdd6423ac479b4da79b7de55c5a9f339b675c46299db184c11"
],
"version": "==2.0.1"
},
"django-storages": { "django-storages": {
"hashes": [ "hashes": [
"sha256:3103991c2ee8cef8a2ff096709973ffe7106183d211a79f22cf855f33533d924", "sha256:3103991c2ee8cef8a2ff096709973ffe7106183d211a79f22cf855f33533d924",
@ -90,6 +110,13 @@
], ],
"version": "==2.7" "version": "==2.7"
}, },
"et-xmlfile": {
"hashes": [
"sha256:1c702bcfd00f9a602dffd0e004d9b4f68a65c7410963ca76b1250413e9197a6c",
"sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b"
],
"version": "==1.0.1"
},
"faker": { "faker": {
"hashes": [ "hashes": [
"sha256:047d4d1791bfb3756264da670d99df13d799bb36e7d88774b1585a82d05dbaec", "sha256:047d4d1791bfb3756264da670d99df13d799bb36e7d88774b1585a82d05dbaec",
@ -104,6 +131,13 @@
], ],
"version": "==20.0.4" "version": "==20.0.4"
}, },
"jdcal": {
"hashes": [
"sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba",
"sha256:472872e096eb8df219c23f2689fc336668bdb43d194094b5cc1707e1640acfc8"
],
"version": "==1.4.1"
},
"jmespath": { "jmespath": {
"hashes": [ "hashes": [
"sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6", "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
@ -111,6 +145,12 @@
], ],
"version": "==0.9.4" "version": "==0.9.4"
}, },
"markuppy": {
"hashes": [
"sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f"
],
"version": "==1.14"
},
"numpy": { "numpy": {
"hashes": [ "hashes": [
"sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6",
@ -137,6 +177,18 @@
], ],
"version": "==1.18.1" "version": "==1.18.1"
}, },
"odfpy": {
"hashes": [
"sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec"
],
"version": "==1.4.1"
},
"openpyxl": {
"hashes": [
"sha256:547a9fc6aafcf44abe358b89ed4438d077e9d92e4f182c87e2dc294186dc4b64"
],
"version": "==3.0.3"
},
"pandas": { "pandas": {
"hashes": [ "hashes": [
"sha256:23e177d43e4bf68950b0f8788b6a2fef2f478f4ec94883acb627b9264522a98a", "sha256:23e177d43e4bf68950b0f8788b6a2fef2f478f4ec94883acb627b9264522a98a",
@ -248,6 +300,22 @@
], ],
"version": "==2019.3" "version": "==2019.3"
}, },
"pyyaml": {
"hashes": [
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
],
"version": "==5.3"
},
"retrying": { "retrying": {
"hashes": [ "hashes": [
"sha256:08c039560a6da2fe4f2c426d0766e284d3b736e355f8dd24b37367b0bb41973b" "sha256:08c039560a6da2fe4f2c426d0766e284d3b736e355f8dd24b37367b0bb41973b"
@ -275,6 +343,13 @@
], ],
"version": "==0.3.0" "version": "==0.3.0"
}, },
"tablib": {
"hashes": [
"sha256:00654241e5beee437ba544e4fa4abef70ccec3668503aa95406c1250bb660770",
"sha256:6336e7aa3f0e5894b47270a3dc639cc2e78eb823c7ccb8c4512fb408cdc18d08"
],
"version": "==0.14.0"
},
"text-unidecode": { "text-unidecode": {
"hashes": [ "hashes": [
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8",
@ -296,6 +371,20 @@
"sha256:62556265ec1011bd87113fb81b7516f52688887b7a010ee899ff1fd18fd22700" "sha256:62556265ec1011bd87113fb81b7516f52688887b7a010ee899ff1fd18fd22700"
], ],
"version": "==5.0.1" "version": "==5.0.1"
},
"xlrd": {
"hashes": [
"sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2",
"sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde"
],
"version": "==1.2.0"
},
"xlwt": {
"hashes": [
"sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e",
"sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"
],
"version": "==1.3.0"
} }
}, },
"develop": { "develop": {
@ -351,10 +440,10 @@
}, },
"coveralls": { "coveralls": {
"hashes": [ "hashes": [
"sha256:2da39aeaef986757653f0a442ba2bef22a8ec602c8bacbc69d39f468dfae12ec", "sha256:4b6bfc2a2a77b890f556bc631e35ba1ac21193c356393b66c84465c06218e135",
"sha256:906e07a12b2ac04b8ad782d06173975fe5ff815fe9df3bfedd2c099bc5791aec" "sha256:67188c7ec630c5f708c31552f2bcdac4580e172219897c4136504f14b823132f"
], ],
"version": "==1.10.0" "version": "==1.11.1"
}, },
"docopt": { "docopt": {
"hashes": [ "hashes": [

View File

@ -40,6 +40,7 @@ INSTALLED_APPS = [
'widget_tweaks', 'widget_tweaks',
'easy_thumbnails', 'easy_thumbnails',
'storages', 'storages',
'import_export',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
@ -215,6 +216,12 @@ REST_FRAMEWORK = {
'PAGE_SIZE': 100 'PAGE_SIZE': 100
} }
# Import/Export configuration
# See https://django-import-export.readthedocs.io/
IMPORT_EXPORT_IMPORT_PERMISSION_CODE = 'add'
IMPORT_EXPORT_EXPORT_PERMISSION_CODE = 'change'
# Baby Buddy configuration # Baby Buddy configuration
# See README.md#configuration for details about these settings. # See README.md#configuration for details about these settings.

View File

@ -2,54 +2,95 @@
from django.contrib import admin from django.contrib import admin
from django.conf import settings from django.conf import settings
from import_export import resources
from import_export.admin import ImportExportMixin
from core import models from core import models
class ChildImportExportResource(resources.ModelResource):
class Meta:
model = models.Child
exclude = ('picture', 'slug')
@admin.register(models.Child) @admin.register(models.Child)
class ChildAdmin(admin.ModelAdmin): class ChildAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'birth_date', 'slug') list_display = ('first_name', 'last_name', 'birth_date', 'slug')
list_filter = ('last_name',) list_filter = ('last_name',)
search_fields = ('first_name', 'last_name', 'birth_date') search_fields = ('first_name', 'last_name', 'birth_date')
fields = ['first_name', 'last_name', 'birth_date'] fields = ['first_name', 'last_name', 'birth_date']
if settings.BABY_BUDDY['ALLOW_UPLOADS']: if settings.BABY_BUDDY['ALLOW_UPLOADS']:
fields.append('picture') fields.append('picture')
resource_class = ChildImportExportResource
class DiaperChangeImportExportResource(resources.ModelResource):
class Meta:
model = models.DiaperChange
@admin.register(models.DiaperChange) @admin.register(models.DiaperChange)
class DiaperChangeAdmin(admin.ModelAdmin): class DiaperChangeAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('child', 'time', 'wet', 'solid', 'color') list_display = ('child', 'time', 'wet', 'solid', 'color')
list_filter = ('child', 'wet', 'solid', 'color') list_filter = ('child', 'wet', 'solid', 'color')
search_fields = ('child__first_name', 'child__last_name',) search_fields = ('child__first_name', 'child__last_name',)
resource_class = DiaperChangeImportExportResource
class FeedingImportExportResource(resources.ModelResource):
class Meta:
model = models.Feeding
@admin.register(models.Feeding) @admin.register(models.Feeding)
class FeedingAdmin(admin.ModelAdmin): class FeedingAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'type', 'method', list_display = ('start', 'end', 'duration', 'child', 'type', 'method',
'amount') 'amount')
list_filter = ('child', 'type', 'method',) list_filter = ('child', 'type', 'method',)
search_fields = ('child__first_name', 'child__last_name', 'type', search_fields = ('child__first_name', 'child__last_name', 'type',
'method',) 'method',)
resource_class = FeedingImportExportResource
class NoteImportExportResource(resources.ModelResource):
class Meta:
model = models.Note
@admin.register(models.Note) @admin.register(models.Note)
class NoteAdmin(admin.ModelAdmin): class NoteAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('time', 'child', 'note',) list_display = ('time', 'child', 'note',)
list_filter = ('child',) list_filter = ('child',)
search_fields = ('child__last_name',) search_fields = ('child__last_name',)
resource_class = NoteImportExportResource
class SleepImportExportResource(resources.ModelResource):
class Meta:
model = models.Sleep
exclude = ('duration',)
@admin.register(models.Sleep) @admin.register(models.Sleep)
class SleepAdmin(admin.ModelAdmin): class SleepAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'nap') list_display = ('start', 'end', 'duration', 'child', 'nap')
list_filter = ('child',) list_filter = ('child',)
search_fields = ('child__first_name', 'child__last_name',) search_fields = ('child__first_name', 'child__last_name',)
resource_class = SleepImportExportResource
class TemperatureImportExportResource(resources.ModelResource):
class Meta:
model = models.Temperature
@admin.register(models.Temperature) @admin.register(models.Temperature)
class TemperatureAdmin(admin.ModelAdmin): class TemperatureAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('child', 'temperature', 'time',) list_display = ('child', 'temperature', 'time',)
list_filter = ('child',) list_filter = ('child',)
search_fields = ('child__first_name', 'child__last_name', 'temperature',) search_fields = ('child__first_name', 'child__last_name', 'temperature',)
resource_class = TemperatureImportExportResource
@admin.register(models.Timer) @admin.register(models.Timer)
@ -60,15 +101,27 @@ class TimerAdmin(admin.ModelAdmin):
search_fields = ('child__first_name', 'child__last_name', 'name', 'user') search_fields = ('child__first_name', 'child__last_name', 'name', 'user')
class TummyTimeImportExportResource(resources.ModelResource):
class Meta:
model = models.TummyTime
@admin.register(models.TummyTime) @admin.register(models.TummyTime)
class TummyTimeAdmin(admin.ModelAdmin): class TummyTimeAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'milestone',) list_display = ('start', 'end', 'duration', 'child', 'milestone',)
list_filter = ('child',) list_filter = ('child',)
search_fields = ('child__first_name', 'child__last_name', 'milestone',) search_fields = ('child__first_name', 'child__last_name', 'milestone',)
resource_class = TummyTimeImportExportResource
class WeightImportExportResource(resources.ModelResource):
class Meta:
model = models.Weight
@admin.register(models.Weight) @admin.register(models.Weight)
class WeightAdmin(admin.ModelAdmin): class WeightAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('child', 'weight', 'date',) list_display = ('child', 'weight', 'date',)
list_filter = ('child',) list_filter = ('child',)
search_fields = ('child__first_name', 'child__last_name', 'weight',) search_fields = ('child__first_name', 'child__last_name', 'weight',)
resource_class = WeightImportExportResource

View File

@ -0,0 +1,22 @@
(function($) {
$(document).ready(function() {
var $actionsSelect, $formatsElement;
if ($('body').hasClass('grp-change-list')) {
// using grappelli
$actionsSelect = $('#grp-changelist-form select[name="action"]');
$formatsElement = $('#grp-changelist-form select[name="file_format"]');
} else {
// using default admin
$actionsSelect = $('#changelist-form select[name="action"]');
$formatsElement = $('#changelist-form select[name="file_format"]').parent();
}
$actionsSelect.change(function() {
if ($(this).val() === 'export_admin_action') {
$formatsElement.show();
} else {
$formatsElement.hide();
}
});
$actionsSelect.change();
});
})(django.jQuery);

Binary file not shown.

View File

@ -0,0 +1,22 @@
(function($) {
$(document).ready(function() {
var $actionsSelect, $formatsElement;
if ($('body').hasClass('grp-change-list')) {
// using grappelli
$actionsSelect = $('#grp-changelist-form select[name="action"]');
$formatsElement = $('#grp-changelist-form select[name="file_format"]');
} else {
// using default admin
$actionsSelect = $('#changelist-form select[name="action"]');
$formatsElement = $('#changelist-form select[name="file_format"]').parent();
}
$actionsSelect.change(function() {
if ($(this).val() === 'export_admin_action') {
$formatsElement.show();
} else {
$formatsElement.hide();
}
});
$actionsSelect.change();
});
})(django.jQuery);

Binary file not shown.

View File

@ -0,0 +1,81 @@
.import-preview .errors {
position: relative;
}
.validation-error-count {
display: inline-block;
background-color: #e40000;
border-radius: 6px;
color: white;
font-size: 0.9em;
position: relative;
font-weight: bold;
margin-top: -2px;
padding: 0.2em 0.4em;
}
.validation-error-container {
position: absolute;
opacity: 0;
pointer-events: none;
background-color: #ffc1c1;
padding: 14px 15px 10px;
top: 25px;
margin: 0 0 20px 0;
width: 200px;
z-index: 2;
}
table.import-preview tr.skip {
background-color: #d2d2d2;
}
table.import-preview tr.new {
background-color: #bdd8b2;
}
table.import-preview tr.delete {
background-color: #f9bebf;
}
table.import-preview tr.update {
background-color: #fdfdcf;
}
.import-preview td:hover .validation-error-count {
z-index: 3;
}
.import-preview td:hover .validation-error-container {
opacity: 1;
pointer-events: auto;
}
.validation-error-list {
margin: 0;
padding: 0;
}
.validation-error-list li {
list-style: none;
margin: 0;
}
.validation-error-list > li > ul {
margin: 8px 0;
padding: 0;
}
.validation-error-list > li > ul > li {
padding: 0;
margin: 0 0 10px;
line-height: 1.28em;
}
.validation-error-field-label {
display: block;
border-bottom: 1px solid #e40000;
color: #e40000;
text-transform: uppercase;
font-weight: bold;
font-size: 0.85em;
}

Binary file not shown.

81
static/import_export/import.css vendored Normal file
View File

@ -0,0 +1,81 @@
.import-preview .errors {
position: relative;
}
.validation-error-count {
display: inline-block;
background-color: #e40000;
border-radius: 6px;
color: white;
font-size: 0.9em;
position: relative;
font-weight: bold;
margin-top: -2px;
padding: 0.2em 0.4em;
}
.validation-error-container {
position: absolute;
opacity: 0;
pointer-events: none;
background-color: #ffc1c1;
padding: 14px 15px 10px;
top: 25px;
margin: 0 0 20px 0;
width: 200px;
z-index: 2;
}
table.import-preview tr.skip {
background-color: #d2d2d2;
}
table.import-preview tr.new {
background-color: #bdd8b2;
}
table.import-preview tr.delete {
background-color: #f9bebf;
}
table.import-preview tr.update {
background-color: #fdfdcf;
}
.import-preview td:hover .validation-error-count {
z-index: 3;
}
.import-preview td:hover .validation-error-container {
opacity: 1;
pointer-events: auto;
}
.validation-error-list {
margin: 0;
padding: 0;
}
.validation-error-list li {
list-style: none;
margin: 0;
}
.validation-error-list > li > ul {
margin: 8px 0;
padding: 0;
}
.validation-error-list > li > ul > li {
padding: 0;
margin: 0 0 10px;
line-height: 1.28em;
}
.validation-error-field-label {
display: block;
border-bottom: 1px solid #e40000;
color: #e40000;
text-transform: uppercase;
font-weight: bold;
font-size: 0.85em;
}

Binary file not shown.

File diff suppressed because one or more lines are too long