Merge pull request #60 from Nebucatnetzer/dev

Dev
This commit is contained in:
Andreas Zweili 2022-02-07 22:41:58 +01:00 committed by GitHub
commit 095403ff5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 805 additions and 539 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

1
.gitignore vendored
View File

@ -177,3 +177,4 @@ htmlcov/
/static
.idea/
db_data
.direnv

View File

@ -1,15 +0,0 @@
language: python
services:
- docker
before_install:
- cp .env-example .env
- docker-compose build
script:
- make test
branches:
only:
- master

View File

@ -1,4 +1,5 @@
FROM python:3
# Python 3.9.6
FROM python@sha256:736b76eb3f64778646ce0051fb5fed4dfbf67016e51563946230ca8bb40ac687
ENV PYTHONUNBUFFERED 1
ADD . /code
WORKDIR /code

View File

@ -1,44 +1,79 @@
SHELL=/usr/bin/env bash
.PHONY: docker
.DEFAULT_GOAL := run
docker:
export DJANGO_SETTINGS_MODULE=network_inventory.settings.docker; \
docker-compose -f docker-compose-development.yml up
.PHONY: run
run: setup
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
$(find . -name __pycache__ -o -name "*.pyc" -delete) \
python manage.py runserver; \
init:
export DJANGO_SETTINGS_MODULE=network_inventory.settings.docker; \
docker-compose -f docker-compose-development.yml run web python manage.py loaddata network_inventory.yaml
.PHONY: setup
setup: ./venv
( \
source venv/bin/activate; \
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
docker-compose -f docker-compose-development.yml up -d; \
if [ -f .second_run ]; then \
sleep 2; \
python manage.py collectstatic --noinput; \
python manage.py makemigrations; \
python manage.py migrate; \
else \
python manage.py collectstatic --noinput; \
python manage.py makemigrations backups; \
python manage.py makemigrations computers; \
python manage.py makemigrations core; \
python manage.py makemigrations customers; \
python manage.py makemigrations devices; \
python manage.py makemigrations licenses; \
python manage.py makemigrations nets; \
python manage.py makemigrations softwares; \
python manage.py makemigrations users; \
python manage.py makemigrations; \
python manage.py migrate; \
python manage.py loaddata backups; \
python manage.py loaddata computers; \
python manage.py loaddata core; \
python manage.py loaddata devices; \
python manage.py loaddata nets; \
python manage.py loaddata softwares; \
python manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin@example.com', 'password')"; \
touch .second_run; \
fi; \
)
test:
docker-compose -f docker-compose-development.yml run web pytest -nauto --nomigrations --cov=. --cov-report=html
debug:
docker-compose -f docker-compose-development.yml run web pytest --pdb --nomigrations --cov=. --cov-report=html
local:
./venv:
python3 -m venv venv
( \
source venv/bin/activate; \
pip3 install -r requirements/local.txt; \
)
testlocal:
( \
source venv/bin/activate; \
pytest -n6 --ds=network_inventory.settings.local --nomigrations --cov=. --cov-report=html; \
)
.PHONY: clean
clean:
docker-compose -f docker-compose-development.yml down -v
sudo find . \( -name __pycache__ -o -name "*.pyc" \) -delete
sudo rm -rf htmlcov/
sudo rm -f */migrations/0*.py
find . \( -name __pycache__ -o -name "*.pyc" \) -delete
rm -rf htmlcov/
rm -f */migrations/0*.py
rm .second_run
cleanall:
.PHONY: cleanall
cleanall: clean
docker-compose -f docker-compose-development.yml down -v --rmi local
rm -rf venv/
sudo find . \( -name __pycache__ -o -name "*.pyc" \) -delete
sudo rm -rf htmlcov/
sudo rm */migrations/*.py
.PHONY: init
init:
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
python manage.py loaddata network_inventory.yaml
.PHONY: test
test:
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
pytest -nauto --nomigrations --cov=. --cov-report=html
.PHONY: debug
debug:
export DJANGO_SETTINGS_MODULE=network_inventory.settings.local; \
pytest --pdb --nomigrations --cov=. --cov-report=html

View File

@ -1,6 +1,6 @@
# network_inventory
![Test Status](https://github.com/Nebucatnetzer/network_inventory/actions/workflows/tests.yml/badge.svg)
[![.github/workflows/publish.yml](https://github.com/Nebucatnetzer/network_inventory/actions/workflows/publish.yml/badge.svg?branch=master)](https://github.com/Nebucatnetzer/network_inventory/actions/workflows/publish.yml)
I started this project in order to have solution for keeping an
inventory over my various servers and other network equipment.

View File

@ -10,7 +10,7 @@ pytestmark = pytest.mark.django_db
def test_computer_create_form(create_admin_user):
fixture = create_admin_user()
user = mixer.blend("auth.User", customer=fixture['customer'])
user = mixer.blend("core.InventoryUser", customer=fixture['customer'])
form = forms.ComputerCreateForm(user=user, data={})
assert form.is_valid() is False, (
"Should be false because no data was given")

5
core/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import InventoryUser
admin.site.register(InventoryUser, UserAdmin)

View File

@ -2,3 +2,4 @@ from .calendar import DayOfMonth, Month, Weekday
from .category import Category
from .company import Company
from .time import HoursInDay, MinutesInHour
from .user import InventoryUser

5
core/models/user.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib.auth.models import AbstractUser
class InventoryUser(AbstractUser):
pass

View File

@ -36,6 +36,22 @@
</div>
<script src="{% static 'core/js/htmx.js' %}"></script>
{% django_htmx_script %}
<script>
function closeModal() {
var container = document.getElementById("htmx-modal-position")
var backdrop = document.getElementById("modal-backdrop")
var modal = document.getElementById("modal")
modal.classList.remove("show")
backdrop.classList.remove("show")
setTimeout(function () {
container.removeChild(backdrop)
container.removeChild(modal)
}, 200)
}
</script>
</body>
</html>

View File

@ -0,0 +1,15 @@
{% load crispy_forms_tags %}
<div id="modal-backdrop" class="modal-backdrop fade show" style="display:block;"></div>
<div id="modal" class="modal fade show" tabindex="-1" style="display:block;">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ modal_title }}</h5>
</div>
<div class="modal-body">
{% crispy form %}
</div>
</div>
</div>
</div>

View File

@ -4,21 +4,15 @@
{% block content %}
<form id="login" name="login" method="post">
{% csrf_token %}
<p>
<label for="id_username">Username:</label>
<div class="ui input">
<input type="text" name="username" autofocus required id="id_username">
{% csrf_token %}
<div class="input-group mb-3">
<input type="text" class="form-control" name="username" autofocus required id="id_username" placeholder="Username">
</div>
</p>
<p>
<label for="id_password">Password:</label>
<div class="ui input">
<input type="password" name="password" required id="id_password">
<div class="input-group mb-3">
<input type="password" class="form-control" name="password" required id="id_password" placeholder="Password">
</div>
</p>
<p>
<button name="button_login" type="submit" class="ui button">Login</button>
</p>
<p>
<button name="button_login" type="submit" class="btn btn-primary">Login</button>
</p>
</form>
{% endblock %}

View File

@ -31,6 +31,14 @@ class CustomersTable(CoreTable):
text='Users',
args=[A('pk')],
orderable=False)
ad_groups = tables.LinkColumn('ad_groups',
text='AD Groups',
args=[A('pk')],
orderable=False)
mail_groups = tables.LinkColumn('mail_groups',
text='Mail Groups',
args=[A('pk')],
orderable=False)
delete = tables.LinkColumn('customer_delete',
text='delete',
args=[A('pk')], attrs={

View File

@ -5,9 +5,10 @@
{% if request.user.is_superuser %}
<div class="row mb-3">
<div class="col">
<button hx-get="{% url 'htmx_create_customer' %}" hx-target="#htmx-create-customer" class="btn btn-primary">Add Customer</button>
<button hx-get="{% url 'htmx_create_customer' %}" hx-target="#htmx-modal-position" class="btn btn-primary"
_="on htmx:afterOnLoad wait 10ms then add .show to #modal then add .show to #modal-backdrop">Add Customer</button>
</div>
<div class="col" id="htmx-create-customer"></div>
<div class="col" id="htmx-modal-position"></div>
</div>
{% endif %}
<div class="row">

View File

@ -1,12 +1 @@
{% load crispy_forms_tags %}
{% block content %}
<div class="row">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary" hx-post="{% url 'htmx_create_customer' %}" hx-target="#htmx-create-customer">Save</button>
<a href="{% url 'customers' %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
{% endblock %}
{% include "core/partials/modal.html" with form=form modal_title="Add Customer"%}

View File

@ -1,21 +1,49 @@
- model: devices.DeviceManufacturer
fields:
name: Dell
name: Dell #1
- model: devices.DeviceManufacturer
fields:
name: HP
name: HP #2
- model: devices.DeviceManufacturer
fields:
name: Axxiv
name: Axxiv #3
- model: devices.DeviceManufacturer
fields:
name: Acer
name: Acer #4
- model: devices.DeviceManufacturer
fields:
name: Asus
name: Asus #5
- model: devices.DeviceManufacturer
fields:
name: Lenovo
name: Lenovo #6
- model: devices.DeviceManufacturer
fields:
name: Samsung
name: Samsung #7
- model: devices.DeviceManufacturer
fields:
name: Apple #8
- model: devices.HardwareModel
fields:
name: MacBook Pro
manufacturer: 8
- model: devices.HardwareModel
fields:
name: MacBook Air
manufacturer: 8
- model: devices.HardwareModel
fields:
name: Latidude 74XX
manufacturer: 1
- model: devices.HardwareModel
fields:
name: Latidude 75XX
manufacturer: 1
- model: devices.HardwareModel
fields:
name: Latidude 54XX
manufacturer: 1
- model: devices.HardwareModel
fields:
name: Latidude 55XX
manufacturer: 1

View File

@ -1,4 +1,8 @@
from django.urls import reverse_lazy
import floppyforms.__future__ as forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, HTML, Field, Button, Div
from crispy_forms.bootstrap import FormActions
from core import utils
@ -6,12 +10,37 @@ from customers.models import Customer
from customers.models import Location
from devices.models import Device
from devices.models import DeviceCategory
from devices.models import DeviceInNet
from devices.models import Warranty
from users.models import User
class DeviceCategoryForm(forms.ModelForm):
class Meta:
model = DeviceCategory
fields = (
'name',
)
def __init__(self, *args, **kwargs):
super(DeviceCategoryForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.attrs = {
'hx-post': reverse_lazy('device_category_create'),
'id': 'device-category-form',
}
self.helper.layout = Layout(
Field('name'),
FormActions(
Submit('save_category', 'Save'),
Button('cancel', 'Cancel', css_class="btn btn-secondary",
onclick="closeModal()")
))
class DeviceCreateForm(forms.ModelForm):
class Meta:
model = Device
@ -47,6 +76,26 @@ class DeviceUpdateForm(forms.ModelForm):
self.fields['customer'].queryset = customers
self.fields['location'].queryset = locations
self.fields['user'].queryset = users
self.helper = FormHelper()
self.helper.form_id = 'htmx-device-form'
self.helper.layout = Layout(
'name',
'customer',
'location',
'user',
Field('category'),
HTML("""
<a hx-get="{% url 'device_category_create' %}" hx-swap="innerHTML" hx-target="#htmx-modal-position" href="" class="add" title="Add" data-toggle="tooltip"><i class="material-icons">add</i></a>
"""),
'serialnumber',
'description',
FormActions(
Submit('save_device', 'Save'),
HTML(
"""<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-secondary">Cancel</a>""")
),
Div(css_id='htmx-modal-position', css_class="col")
)
class Meta:
model = Device

View File

@ -3,25 +3,8 @@
{% block section_title %}Edit Device{% endblock %}
{% block content %}
<form method="post">
<div class="row">
{% csrf_token %}
<div class="col">
<div class="card mt-3">
<div class="card-body">
{{ form|crispy }}
</div>
</div>
</div>
</div>
<br>
<input type="submit" value="Save" class="btn btn-primary">
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-primary">Cancel</a>
<input type="hidden" id="previous_page" name="previous_page" value="/previous/page/url">
</form>
<script>
prev = document.getElementById("previous_page");
prev.value = document.referrer;
</script>
<div id="device-update">
{% crispy form %}
</div>
{% endblock %}

View File

@ -0,0 +1 @@
{% include "core/partials/modal.html" with form=form modal_title="Add Device Category"%}

View File

@ -0,0 +1,6 @@
{% if valid %}
<div hx-swap-oob="true:#div_id_category">{{ form }}</div>
<div id="htmx-modal-position" hx-swap-oob="true"></div>
{% else %}
{{ form }}
{% endif %}

View File

@ -10,7 +10,7 @@ pytestmark = pytest.mark.django_db
def test_device_create_form(create_admin_user):
fixture = create_admin_user()
user = mixer.blend("auth.User", customer=fixture['customer'])
user = mixer.blend("core.InventoryUser", customer=fixture['customer'])
form = forms.DeviceCreateForm(user=user, data={})
assert form.is_valid() is False, (
"Should be false because no data was given")
@ -49,7 +49,7 @@ def test_device_update_form(create_admin_user):
def test_device_create_form_duplicate_device(create_admin_user):
fixture = create_admin_user()
user = mixer.blend("auth.User", customer=fixture['customer'])
user = mixer.blend("core.InventoryUser", customer=fixture['customer'])
mixer.blend("devices.Device", name="pharma-device1",
customer=fixture['customer'])
data = {"name": "pharma-device1",

View File

@ -35,4 +35,6 @@ urlpatterns = [
views.DeviceInNetDeleteView.as_view(),
name='device_in_net_delete'),
path('warranties/', views.warranties_view, name='warranties'),
path('create/devices/category', views.htmx_create_device_cagetory,
name='device_category_create')
]

View File

@ -2,6 +2,7 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import render
from django.template.context_processors import csrf
from django.template.response import TemplateResponse
from django.urls import reverse
from django.views.generic import CreateView
@ -9,6 +10,8 @@ from django.views.generic import DetailView
from django.views.generic import UpdateView
from django.views.generic import DeleteView
from crispy_forms.utils import render_crispy_form
from crispy_forms.templatetags.crispy_forms_filters import as_crispy_field
from django_tables2 import RequestConfig
from customers.decorators import customer_view_permission
@ -17,6 +20,7 @@ from core import utils
from .decorators import device_view_permission
from .forms import DeviceCategoryForm
from .forms import DeviceCreateForm
from .forms import DeviceInNetCreateForm
from .forms import DeviceInNetUpdateForm
@ -102,11 +106,12 @@ def device_update_view(request, pk):
"""
A view to create a customer.
"""
template_name = 'computers/computer_update.html'
template_name = 'devices/device_update.html'
request.session['device_to_update'] = pk
device = utils.get_object_with_view_permission(Device,
user=request.user,
pk=pk)
if request.method == 'POST':
if request.method == "POST" and 'save_device' in request.POST:
form = DeviceUpdateForm(request, request.POST, instance=device)
if form.is_valid():
device = form.save()
@ -198,3 +203,35 @@ class DeviceInNetDeleteView(LoginRequiredMixin, DeleteView):
class DeviceManufacturerDetailView(LoginRequiredMixin, DetailView):
model = DeviceManufacturer
template_name = 'devices/manufacturer_details.html'
@login_required
def htmx_create_device_cagetory(request):
context = {}
if request.method == "POST" and 'save_category' in request.POST:
form = DeviceCategoryForm(request.POST)
if form.is_valid():
category = form.save(commit=True)
pk = request.session.get('device_to_update')
device = utils.get_object_with_view_permission(Device,
user=request.user,
pk=pk)
device.category = category
device_form = DeviceUpdateForm(request, instance=device)
form_html = as_crispy_field(device_form['category'])
else:
context.update(csrf(request))
form.helper.attrs['hx-swap-oob'] = 'true'
form_html = render_crispy_form(form, context=context)
context["valid"] = form.is_valid()
context["form"] = form_html
template_path = "devices/partials/device_category_response.html"
return TemplateResponse(request,
template_path,
context)
form = DeviceCategoryForm()
context["form"] = form
template_path = "devices/partials/device_category_create.html"
return TemplateResponse(request,
template_path,
context)

View File

@ -7,27 +7,9 @@ services:
db:
image: postgres
environment:
- POSTGRES_DB
- POSTGRES_PASSWORD
- POSTGRES_DB=network_inventory
- POSTGRES_PASSWORD=password
volumes:
- db_data:/var/lib/postgresql/data/
web:
build: .
volumes:
- .:/code
environment:
- DJANGO_SETTINGS_MODULE
- DJANGO_DEBUG
- DJANGO_SECRET_KEY
depends_on:
- db
nginx:
build: ./nginx
ports:
- 8080:80
depends_on:
- web
volumes:
- ./static:/home/app/web/static
- "5432:5432"

View File

@ -11,6 +11,7 @@ services:
web:
image: nebucatnetzer/network_inventory
# build: .
volumes:
- .:/code
environment:

View File

@ -2,11 +2,11 @@
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1638122382,
"narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=",
"lastModified": 1642700792,
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "74f7e4319258e287b0f9cb95426c9853b282730b",
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
"type": "github"
},
"original": {
@ -17,11 +17,11 @@
},
"flake-utils_2": {
"locked": {
"lastModified": 1601282935,
"narHash": "sha256-WQAFV6sGGQxrRs3a+/Yj9xUYvhTpukQJIcMbIi7LCJ4=",
"lastModified": 1642700792,
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "588973065fce51f4763287f0fda87a174d78bf48",
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
"type": "github"
},
"original": {
@ -37,26 +37,26 @@
"pypi-deps-db": "pypi-deps-db"
},
"locked": {
"lastModified": 1637687243,
"narHash": "sha256-Qm0hPR9ZT1EP7lRW4udAPeb4yv6D2ONcw9ayterTP18=",
"lastModified": 1643953409,
"narHash": "sha256-CJDg/RpZdUVyI3QIAXUqIoYDl7VkxFtNE4JWih0ucKc=",
"owner": "DavHau",
"repo": "mach-nix",
"rev": "31b21203a1350bff7c541e9dfdd4e07f76d874be",
"rev": "fe5255e6fd8df57e9507b7af82fc59dda9e9ff2b",
"type": "github"
},
"original": {
"owner": "DavHau",
"repo": "mach-nix",
"type": "github"
"id": "mach-nix",
"ref": "3.4.0",
"type": "indirect"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1622797669,
"narHash": "sha256-xIyWeoYExzF0KNaKcqfxEX58fN4JTIQxTJWbsAujllc=",
"lastModified": 1643805626,
"narHash": "sha256-AXLDVMG+UaAGsGSpOtQHPIKB+IZ0KSd9WS77aanGzgc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1ca6b0a0cc38dbba0441202535c92841dd39d1ae",
"rev": "554d2d8aa25b6e583575459c297ec23750adb6cb",
"type": "github"
},
"original": {
@ -67,11 +67,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1638887115,
"narHash": "sha256-emjtIeqyJ84Eb3X7APJruTrwcfnHQKs55XGljj62prs=",
"lastModified": 1643503720,
"narHash": "sha256-tJic20ufuRnG8V+fTCd3YU6xl1ImxNspoEkXHct0AG4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1bd4bbd49bef217a3d1adea43498270d6e779d65",
"rev": "0f316e4d72daed659233817ffe52bf08e081b5de",
"type": "github"
},
"original": {
@ -84,11 +84,11 @@
"pypi-deps-db": {
"flake": false,
"locked": {
"lastModified": 1622970040,
"narHash": "sha256-u//RFnae/XMIhoy83G2uH2Qu/1LiUhVCdwwY1xj4Ufs=",
"lastModified": 1643919960,
"narHash": "sha256-YhB/Jx//oCoMRPPbEeXzwg/R2FbrZygIm1heJFHcxmI=",
"owner": "DavHau",
"repo": "pypi-deps-db",
"rev": "be6591698c67a86a69c81fef72167e38d038a9fc",
"rev": "e37d526a241f7568879fe2ec791a85b3e6a09fa5",
"type": "github"
},
"original": {

View File

@ -2,18 +2,33 @@
description = "A Python API for various tools I use at work.";
inputs = {
nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
flake-utils.url = github:numtide/flake-utils;
mach-nix.url = "github:DavHau/mach-nix";
flake-utils = {
url = github:numtide/flake-utils;
};
mach-nix = {
url = "mach-nix/3.4.0";
};
};
outputs = { self, nixpkgs, flake-utils, mach-nix }:
with flake-utils.lib; eachSystem allSystems (system:
outputs = { self, nixpkgs, flake-utils, mach-nix, ... }@inputs:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
machNix = mach-nix.lib."${system}";
in
rec {
devShell = pkgs.mkShell {
buildInputs = [ pkgs.gnumake ];
{
devShell = machNix.mkPythonShell {
packagesExtra = with pkgs; [ pkgs.gnumake ];
requirements = builtins.readFile ./requirements/local.txt;
_.pytest-cov.propagatedBuildInputs.mod = pySelf: self: oldVal: oldVal ++ [ pySelf.tomli ];
};
defaultPackage = (machNix.mkDockerImage {
packagesExtra = with pkgs; [ pkgs.bash ];
requirements = builtins.readFile ./requirements/docker.txt;
_.pytest-cov.propagatedBuildInputs.mod = pySelf: self: oldVal: oldVal ++ [ pySelf.tomli ];
}).override (oldAttrs: {
name = "network-inventory";
config.Cmd = [ "run.sh" ];
});
});
}

View File

@ -362,326 +362,6 @@
valid_from: 2020-06-01
valid_until: 2023-07-13
warranty_type: 2
- model: contenttypes.contenttype
pk: 1
fields:
app_label: backups
model: backup
- model: contenttypes.contenttype
pk: 2
fields:
app_label: backups
model: backupmethod
- model: contenttypes.contenttype
pk: 3
fields:
app_label: backups
model: notification
- model: contenttypes.contenttype
pk: 4
fields:
app_label: backups
model: notificationtype
- model: contenttypes.contenttype
pk: 5
fields:
app_label: backups
model: targetdevice
- model: contenttypes.contenttype
pk: 6
fields:
app_label: backups
model: notificationfrombackup
- model: contenttypes.contenttype
pk: 7
fields:
app_label: computers
model: computer
- model: contenttypes.contenttype
pk: 8
fields:
app_label: computers
model: cpuarchitecture
- model: contenttypes.contenttype
pk: 9
fields:
app_label: computers
model: cpumanufacturer
- model: contenttypes.contenttype
pk: 10
fields:
app_label: computers
model: disk
- model: contenttypes.contenttype
pk: 11
fields:
app_label: computers
model: disktype
- model: contenttypes.contenttype
pk: 12
fields:
app_label: computers
model: gpumanufacturer
- model: contenttypes.contenttype
pk: 13
fields:
app_label: computers
model: raidtype
- model: contenttypes.contenttype
pk: 14
fields:
app_label: computers
model: ramtype
- model: contenttypes.contenttype
pk: 15
fields:
app_label: computers
model: ram
- model: contenttypes.contenttype
pk: 16
fields:
app_label: computers
model: raid
- model: contenttypes.contenttype
pk: 17
fields:
app_label: computers
model: gpu
- model: contenttypes.contenttype
pk: 18
fields:
app_label: computers
model: disksinraid
- model: contenttypes.contenttype
pk: 19
fields:
app_label: computers
model: cpu
- model: contenttypes.contenttype
pk: 20
fields:
app_label: computers
model: computersoftwarerelation
- model: contenttypes.contenttype
pk: 21
fields:
app_label: computers
model: computerramrelation
- model: contenttypes.contenttype
pk: 22
fields:
app_label: computers
model: computergpurelation
- model: contenttypes.contenttype
pk: 23
fields:
app_label: computers
model: computerdiskrelation
- model: contenttypes.contenttype
pk: 24
fields:
app_label: computers
model: computercpurelation
- model: contenttypes.contenttype
pk: 25
fields:
app_label: core
model: dayofmonth
- model: contenttypes.contenttype
pk: 26
fields:
app_label: core
model: hoursinday
- model: contenttypes.contenttype
pk: 27
fields:
app_label: core
model: minutesinhour
- model: contenttypes.contenttype
pk: 28
fields:
app_label: core
model: month
- model: contenttypes.contenttype
pk: 29
fields:
app_label: core
model: weekday
- model: contenttypes.contenttype
pk: 30
fields:
app_label: customers
model: customer
- model: contenttypes.contenttype
pk: 31
fields:
app_label: customers
model: devicemanufacturer
- model: contenttypes.contenttype
pk: 32
fields:
app_label: customers
model: owner
- model: contenttypes.contenttype
pk: 33
fields:
app_label: customers
model: location
- model: contenttypes.contenttype
pk: 34
fields:
app_label: devices
model: device
- model: contenttypes.contenttype
pk: 35
fields:
app_label: devices
model: devicecategory
- model: contenttypes.contenttype
pk: 36
fields:
app_label: devices
model: devicemanufacturer
- model: contenttypes.contenttype
pk: 37
fields:
app_label: devices
model: warrantytype
- model: contenttypes.contenttype
pk: 38
fields:
app_label: devices
model: warranty
- model: contenttypes.contenttype
pk: 39
fields:
app_label: devices
model: hardwaremodel
- model: contenttypes.contenttype
pk: 40
fields:
app_label: devices
model: deviceinnet
- model: contenttypes.contenttype
pk: 41
fields:
app_label: admin
model: logentry
- model: contenttypes.contenttype
pk: 42
fields:
app_label: auth
model: permission
- model: contenttypes.contenttype
pk: 43
fields:
app_label: auth
model: group
- model: contenttypes.contenttype
pk: 44
fields:
app_label: auth
model: user
- model: contenttypes.contenttype
pk: 45
fields:
app_label: contenttypes
model: contenttype
- model: contenttypes.contenttype
pk: 46
fields:
app_label: sessions
model: session
- model: contenttypes.contenttype
pk: 47
fields:
app_label: guardian
model: groupobjectpermission
- model: contenttypes.contenttype
pk: 48
fields:
app_label: guardian
model: userobjectpermission
- model: contenttypes.contenttype
pk: 49
fields:
app_label: licenses
model: computerlicense
- model: contenttypes.contenttype
pk: 50
fields:
app_label: licenses
model: licensewithuser
- model: contenttypes.contenttype
pk: 51
fields:
app_label: licenses
model: userlicense
- model: contenttypes.contenttype
pk: 52
fields:
app_label: licenses
model: licensewithcomputer
- model: contenttypes.contenttype
pk: 53
fields:
app_label: nets
model: ipstatus
- model: contenttypes.contenttype
pk: 54
fields:
app_label: nets
model: net
- model: contenttypes.contenttype
pk: 55
fields:
app_label: softwares
model: softwarearchitecture
- model: contenttypes.contenttype
pk: 56
fields:
app_label: softwares
model: softwarecategory
- model: contenttypes.contenttype
pk: 57
fields:
app_label: softwares
model: software
- model: contenttypes.contenttype
pk: 58
fields:
app_label: softwares
model: operatingsystem
- model: contenttypes.contenttype
pk: 59
fields:
app_label: users
model: adgroup
- model: contenttypes.contenttype
pk: 60
fields:
app_label: users
model: mailgroup
- model: contenttypes.contenttype
pk: 61
fields:
app_label: users
model: user
- model: contenttypes.contenttype
pk: 62
fields:
app_label: users
model: userinmailgroup
- model: contenttypes.contenttype
pk: 63
fields:
app_label: users
model: userinadgroup
- model: contenttypes.contenttype
pk: 64
fields:
app_label: users
model: mailalias
- model: licenses.userlicense
pk: 1
fields:
@ -2468,31 +2148,6 @@
name: Can view mail alias
content_type: 64
codename: view_mailalias
- model: auth.user
pk: 1
fields:
password: '!c7GSQMX3Yx9m7XfSx94UzysOaUSKknTlsfWg1wBQ'
last_login: null
is_superuser: false
username: AnonymousUser
first_name: ''
last_name: ''
email: ''
is_staff: false
is_active: true
date_joined: 2020-06-09 19:41:33.728056+00:00
groups: []
user_permissions: []
- model: admin.logentry
pk: 1
fields:
action_time: 2020-06-15 10:59:03.336794+00:00
user: 2
content_type: 30
object_id: '1'
object_repr: Pharma Company
action_flag: 2
change_message: '[{"changed": {"fields": ["name"]}}]'
- model: admin.logentry
pk: 2
fields:

View File

@ -105,7 +105,7 @@ AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # this is default
'guardian.backends.ObjectPermissionBackend',
)
AUTH_USER_MODEL = 'core.InventoryUser'
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
@ -129,3 +129,5 @@ STATIC_ROOT = os.path.join(BASE_DIR, "..", "static")
DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap4.html"
CRISPY_TEMPLATE_PACK = 'bootstrap4'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

View File

@ -3,6 +3,7 @@ from .base import *
ALLOWED_HOSTS = [
'localhost',
'127.0.0.1',
'10.7.89.104'
]
CSRF_TRUSTED_ORIGINS = [
@ -13,6 +14,7 @@ CSRF_TRUSTED_ORIGINS = [
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
DEBUG = os.environ.get('DJANGO_DEBUG')
CRISPY_FAIL_SILENTLY = not DEBUG
DATABASES = {
'default': {

View File

@ -5,15 +5,23 @@ ALLOWED_HOSTS = [
'127.0.0.1',
]
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'development_key'
CSRF_TRUSTED_ORIGINS = [
'http://localhost:8000',
]
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "foo"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
CRISPY_FAIL_SILENTLY = not DEBUG
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres',
'USER': 'postgres',
'HOST': 'localhost',
'PORT': 5432,
'PASSWORD': 'password',
}
}

View File

@ -9,6 +9,7 @@ SECRET_KEY = os.getenv('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
CRISPY_FAIL_SILENTLY = not DEBUG
DATABASES = {
'default': {

View File

@ -26,6 +26,6 @@ urlpatterns = [
path(r'', include('licenses.urls')),
path(r'', include('nets.urls')),
path(r'', include('users.urls')),
path('admin/', admin.site.urls),
path('management/', admin.site.urls),
path('_nested_admin/', include('nested_admin.urls')),
]

View File

@ -1,16 +0,0 @@
wheel
Django
pyaml
pytz
django-guardian
django-tables2
django-filter
django-nested-admin
django-floppyforms
django-crispy-forms
django-htmx
pytest
pytest-django
pytest-cov
pytest-xdist
mixer

View File

@ -1,4 +1,36 @@
-r base.txt
psycopg2==2.8.6
gunicorn
asgiref==3.5.0
attrs==21.4.0
coverage==6.3
Django==4.0.1
django-crispy-forms==1.14.0
django-filter==21.1
django-floppyforms==1.9.0
django-guardian==2.4.0
django-htmx==1.8.0
django-nested-admin==3.4.0
django-tables2==2.4.1
execnet==1.9.0
Faker==11.3.0
gunicorn==20.1.0
iniconfig==1.1.1
mixer==7.2.0
packaging==21.3
pluggy==1.0.0
psycopg2-binary==2.8.6
py==1.11.0
pyaml==21.10.1
pyparsing==3.0.7
pytest==6.2.5
pytest-cov==3.0.0
pytest-django==4.5.2
pytest-forked==1.4.0
pytest-xdist==2.5.0
python-dateutil==2.8.2
python-monkey-business==1.0.0
pytz==2021.3
PyYAML==6.0
six==1.16.0
sqlparse==0.4.2
text-unidecode==1.3
toml==0.10.2
tomli==2.0.0

View File

@ -1,10 +1,56 @@
-r base.txt
pep8
rope
pylint
jedi
autopep8
yapf
black
flake8
asgiref==3.4.1
astroid==2.9.0
attrs==21.4.0
autopep8==1.6.0
black==21.12b0
click==8.0.3
coverage==6.2
Django==4.0.1
django-crispy-forms==1.14.0
django-filter==21.1
django-floppyforms==1.9.0
django-guardian==2.4.0
django-htmx==1.8.0
django-nested-admin==3.4.0
django-tables2==2.4.1
execnet==1.9.0
Faker==11.1.0
flake8==4.0.1
iniconfig==1.1.1
isort==5.10.1
jedi==0.18.1
lazy-object-proxy==1.7.1
mccabe==0.6.1
mixer==7.2.0
mypy-extensions==0.4.3
packaging==21.3
parso==0.8.3
pathspec==0.9.0
pep8==1.7.1
platformdirs==2.4.1
pluggy==1.0.0
py==1.11.0
pyaml==21.10.1
pycodestyle==2.8.0
pyflakes==2.4.0
pylint==2.12.2
pyparsing==3.0.6
psycopg2-binary==2.8.6
pytest==6.2.5
pytest-cov==3.0.0
pytest-django==4.5.2
pytest-forked==1.4.0
pytest-xdist==2.5.0
python-dateutil==2.8.2
python-monkey-business==1.0.0
pytz==2021.3
PyYAML==6.0
rope==0.22.0
six==1.16.0
sqlparse==0.4.2
text-unidecode==1.3
toml==0.10.2
tomli==1.2.3
typing-extensions==4.0.1
wrapt==1.13.3
yapf==0.32.0

5
run.sh
View File

@ -1,5 +1,5 @@
#!/bin/bash
if [ -f /var/cache/network_inventory/.second_run ]; then
if [ -f .second_run ]; then
sleep 2
python manage.py collectstatic --noinput
python manage.py makemigrations
@ -24,8 +24,7 @@ else
python manage.py loaddata nets
python manage.py loaddata softwares
python manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin@example.com', 'password')"
mkdir -p /var/cache/network_inventory
touch /var/cache/network_inventory/.second_run
touch .second_run
fi
find . \( -name __pycache__ -o -name "*.pyc" \) -delete
gunicorn network_inventory.wsgi:application --reload --bind 0.0.0.0:8000 --workers 3

View File

@ -19,6 +19,10 @@ class AdGroup(Group):
ordering = ['name']
verbose_name_plural = "AD Groups"
def get_absolute_url(self):
from django.urls import reverse
return reverse('ad_group', args=[str(self.id)])
class MailGroup(Group):
mail_address = models.EmailField()
@ -26,3 +30,7 @@ class MailGroup(Group):
class Meta:
ordering = ['name']
verbose_name_plural = "Mail Groups"
def get_absolute_url(self):
from django.urls import reverse
return reverse('mail_group', args=[str(self.id)])

View File

@ -3,6 +3,8 @@ from django_tables2.utils import A
from core.tables import CoreTable
from .models import AdGroup
from .models import MailGroup
from .models import User
@ -27,3 +29,31 @@ class UsersTable(CoreTable):
class Meta(CoreTable.Meta):
model = User
class AdGroupsTable(CoreTable):
id = tables.Column(visible=False)
name = tables.Column('AdGroup', linkify=True)
customer = tables.Column('Customer', linkify=True)
delete = tables.LinkColumn('ad_group_delete',
text='delete',
args=[A('pk')], attrs={
'a': {'class': 'delete material-icons', }
}, orderable=False)
class Meta(CoreTable.Meta):
model = AdGroup
class MailGroupsTable(CoreTable):
id = tables.Column(visible=False)
name = tables.Column('MailGroup', linkify=True)
customer = tables.Column('Customer', linkify=True)
delete = tables.LinkColumn('mail_group_delete',
text='delete',
args=[A('pk')], attrs={
'a': {'class': 'delete material-icons', }
}, orderable=False)
class Meta(CoreTable.Meta):
model = MailGroup

View File

@ -0,0 +1,16 @@
{% extends "core/base.html" %}
{% block section_title %}Delete AD group{% endblock %}
{% block content %}
<div class="row">
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete the AD group "{{ object }}"?</p>
<button type="submit" class="btn btn-danger">Delete</button>
<a href="{% url 'ad_groups' object.customer.pk %}" class="btn btn-primary">Cancel</a>
<input type="hidden" id="previous_page" name="previous_page" value="/previous/page/url">
</form>
<script>
prev = document.getElementById("previous_page");
prev.value = document.referrer;
</script>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends "core/base.html" %}
{% block section_title %}{{ group }}{% endblock %}
{% block content %}
<div class="row">
<div class="col">
<div class="card mt-3">
<div class="card-body">
<div class="card-text">
<ul>
<li>Customer: <a href="{% url 'customer' group.customer.id %}">{{ group.customer }}</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends "core/base.html" %}
{% load render_table from django_tables2 %}
{% block section_title %}Groups{% endblock %}
{% block content %}
<div class="table-responsive">
{% render_table groups %}
</div>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "core/base.html" %}
{% block section_title %}Delete Mail group{% endblock %}
{% block content %}
<div class="row">
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete the mail group "{{ object }}"?</p>
<button type="submit" class="btn btn-danger">Delete</button>
<a href="{% url 'mail_groups' object.customer.pk %}" class="btn btn-primary">Cancel</a>
<input type="hidden" id="previous_page" name="previous_page" value="/previous/page/url">
</form>
<script>
prev = document.getElementById("previous_page");
prev.value = document.referrer;
</script>
</div>
{% endblock %}

View File

@ -0,0 +1,41 @@
import pytest
from mixer.backend.django import mixer
from django.test import Client
from core.tests import helper
pytestmark = pytest.mark.django_db
def test_ad_group_detail_view_not_logged_in():
response = Client().get('/ad-group/1/')
assert response.status_code == 302 and 'login' in response.url
def test_ad_group_detail_view(create_admin_user):
create_admin_user()
group = mixer.blend('users.AdGroup', customer=mixer.SELECT)
client = Client()
client.login(username="pharma-admin", password="password")
response = client.get('/ad-group/' + str(group.id) + '/')
assert (response.status_code == 200
and helper.in_content(response, group))
def test_ad_group_detail_view_not_found(create_admin_user):
create_admin_user()
client = Client()
client.login(username="pharma-admin", password="password")
response = client.get('/ad-group/230/')
assert response.status_code == 404
def test_ad_group_detail_view_no_permission(create_admin_user):
create_admin_user()
customer = mixer.blend('customers.Customer')
group = mixer.blend('users.AdGroup', customer=customer)
client = Client()
client.login(username="pharma-admin", password="password")
response = client.get('/ad-group/' + str(group.id) + '/')
assert response.status_code == 404

View File

@ -0,0 +1,58 @@
import pytest
from django.test import Client
from mixer.backend.django import mixer
from core.tests import helper
from customers.models import Customer
pytestmark = pytest.mark.django_db
def test_customer_ad_group_table_not_logged_in():
response = Client().get('/customer/1/ad-groups/')
assert response.status_code == 302 and 'login' in response.url
def test_customer_ad_group_table(create_admin_user):
fixture = create_admin_user()
customer = fixture['customer']
client = Client()
client.login(username="pharma-admin", password="password")
ad_group = mixer.blend('users.AdGroup', customer=customer)
response = client.get('/customer/' + str(customer.id) + '/ad-groups/')
assert (response.status_code == 200
and helper.in_content(response, ad_group))
def test_customer_ad_group_table_no_group(create_admin_user):
fixture = create_admin_user()
customer = fixture['customer']
client = Client()
client.login(username="pharma-admin", password="password")
response = client.get('/customer/' + str(customer.id) + '/ad-groups/')
assert (response.status_code == 200
and helper.not_in_content(response, customer))
def test_customer_ad_group_table_no_permission(create_admin_user):
create_admin_user()
customer = Customer.objects.create(name='Water Corp.')
client = Client()
client.login(username="pharma-admin", password="password")
mixer.blend('users.AdGroup', customer=customer)
response = client.get('/customer/' + str(customer.id) + '/ad-groups/')
assert response.status_code == 404
def test_customer_ad_group_table_multiple_groups(create_admin_user):
fixture = create_admin_user()
customer = fixture['customer']
client = Client()
client.login(username="pharma-admin", password="password")
group1 = mixer.blend('users.AdGroup', customer=mixer.SELECT)
group2 = mixer.blend('users.AdGroup', customer=mixer.SELECT)
response = client.get('/customer/' + str(customer.id) + '/ad-groups/')
assert (response.status_code == 200
and helper.in_content(response, group1.name)
and helper.in_content(response, group2.name))

View File

@ -0,0 +1,58 @@
import pytest
from django.test import Client
from mixer.backend.django import mixer
from core.tests import helper
from customers.models import Customer
pytestmark = pytest.mark.django_db
def test_customer_mail_group_table_not_logged_in():
response = Client().get('/customer/1/mail-groups/')
assert response.status_code == 302 and 'login' in response.url
def test_customer_mail_group_table(create_admin_user):
fixture = create_admin_user()
customer = fixture['customer']
client = Client()
client.login(username="pharma-admin", password="password")
mail_group = mixer.blend('users.MailGroup', customer=customer)
response = client.get('/customer/' + str(customer.id) + '/mail-groups/')
assert (response.status_code == 200
and helper.in_content(response, mail_group))
def test_customer_mail_group_table_no_group(create_admin_user):
fixture = create_admin_user()
customer = fixture['customer']
client = Client()
client.login(username="pharma-admin", password="password")
response = client.get('/customer/' + str(customer.id) + '/mail-groups/')
assert (response.status_code == 200
and helper.not_in_content(response, customer))
def test_customer_mail_group_table_no_permission(create_admin_user):
create_admin_user()
customer = Customer.objects.create(name='Water Corp.')
client = Client()
client.login(username="pharma-admin", password="password")
mixer.blend('users.MailGroup', customer=customer)
response = client.get('/customer/' + str(customer.id) + '/mail-groups/')
assert response.status_code == 404
def test_customer_mail_group_table_multiple_groups(create_admin_user):
fixture = create_admin_user()
customer = fixture['customer']
client = Client()
client.login(username="pharma-admin", password="password")
group1 = mixer.blend('users.MailGroup', customer=mixer.SELECT)
group2 = mixer.blend('users.MailGroup', customer=mixer.SELECT)
response = client.get('/customer/' + str(customer.id) + '/mail-groups/')
assert (response.status_code == 200
and helper.in_content(response, group1.name)
and helper.in_content(response, group2.name))

View File

@ -0,0 +1,41 @@
import pytest
from mixer.backend.django import mixer
from django.test import Client
from core.tests import helper
pytestmark = pytest.mark.django_db
def test_mail_group_detail_view_not_logged_in():
response = Client().get('/mail-group/1/')
assert response.status_code == 302 and 'login' in response.url
def test_mail_group_detail_view(create_admin_user):
create_admin_user()
group = mixer.blend('users.MailGroup', customer=mixer.SELECT)
client = Client()
client.login(username="pharma-admin", password="password")
response = client.get('/mail-group/' + str(group.id) + '/')
assert (response.status_code == 200
and helper.in_content(response, group))
def test_mail_group_detail_view_not_found(create_admin_user):
create_admin_user()
client = Client()
client.login(username="pharma-admin", password="password")
response = client.get('/mail-group/230/')
assert response.status_code == 404
def test_mail_group_detail_view_no_permission(create_admin_user):
create_admin_user()
customer = mixer.blend('customers.Customer')
group = mixer.blend('users.MailGroup', customer=customer)
client = Client()
client.login(username="pharma-admin", password="password")
response = client.get('/mail-group/' + str(group.id) + '/')
assert response.status_code == 404

View File

@ -3,6 +3,16 @@ from django.urls import path
from . import views
urlpatterns = [
path('customer/<int:pk>/ad-groups/', views.ad_groups_table_view,
name='ad_groups'),
path('customer/<int:pk>/mail-groups/', views.mail_groups_table_view,
name='mail_groups'),
path('ad-group/<int:pk>/', views.ad_group_detail_view, name='ad_group'),
path('mail-group/<int:pk>/', views.mail_group_detail_view, name='mail_group'),
path('delete/ad-group/<int:pk>/', views.delete_ad_group,
name='ad_group_delete'),
path('delete/mail-group/<int:pk>/', views.delete_mail_group,
name='mail_group_delete'),
path('customer/<int:pk>/users/', views.users_table_view,
name='users'),
path('user/<int:pk>/', views.user_detail_view, name='user'),

View File

@ -1,21 +1,28 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404
from django.shortcuts import redirect
from django.shortcuts import render
from django.template.response import TemplateResponse
from django.urls import reverse
from django.views.generic import DeleteView
from django_tables2 import RequestConfig
from core import utils
from customers.decorators import customer_view_permission
from computers.models import Computer
from licenses.models import LicenseWithUser
from .decorators import user_view_permission
from .models import AdGroup
from .models import MailGroup
from .models import MailAlias
from .models import User
from .models import UserInAdGroup
from .models import UserInMailGroup
from .tables import AdGroupsTable
from .tables import MailGroupsTable
from .tables import UsersTable
@ -50,3 +57,69 @@ class UserDeleteView(LoginRequiredMixin, DeleteView):
def get_success_url(self):
return reverse('users', args=(self.object.customer.pk,))
@login_required
def ad_groups_table_view(request, pk):
table = AdGroupsTable(utils.get_objects_for_customer(AdGroup,
user=request.user,
customer_pk=pk))
RequestConfig(request).configure(table)
return TemplateResponse(request,
'groups/group_list.html',
{'groups': table})
@login_required
def ad_group_detail_view(request, pk):
group = utils.get_object_with_view_permission(AdGroup,
user=request.user,
pk=pk)
return render(request, 'groups/group_details.html',
{'group': group})
@login_required
def mail_groups_table_view(request, pk):
table = MailGroupsTable(utils.get_objects_for_customer(MailGroup,
user=request.user,
customer_pk=pk))
RequestConfig(request).configure(table)
return TemplateResponse(request,
'groups/group_list.html',
{'groups': table})
@login_required
def mail_group_detail_view(request, pk):
group = utils.get_object_with_view_permission(MailGroup,
user=request.user,
pk=pk)
return render(request, 'groups/group_details.html',
{'group': group})
@login_required
def delete_ad_group(request, pk):
group = utils.get_object_with_view_permission(AdGroup,
user=request.user,
pk=pk)
if request.method == 'POST':
group.delete()
return redirect('ad_groups', pk=group.customer.pk)
return TemplateResponse(request,
'groups/ad_group_confirm_delete.html',
{'object': group})
@login_required
def delete_mail_group(request, pk):
group = utils.get_object_with_view_permission(MailGroup,
user=request.user,
pk=pk)
if request.method == 'POST':
group.delete()
return redirect('mail_groups', pk=group.customer.pk)
return TemplateResponse(request,
'groups/mail_group_confirm_delete.html',
{'object': group})