commit
095403ff5e
|
@ -177,3 +177,4 @@ htmlcov/
|
|||
/static
|
||||
.idea/
|
||||
db_data
|
||||
.direnv
|
||||
|
|
15
.travis.yml
15
.travis.yml
|
@ -1,15 +0,0 @@
|
|||
language: python
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- cp .env-example .env
|
||||
- docker-compose build
|
||||
|
||||
script:
|
||||
- make test
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
|
@ -1,4 +1,5 @@
|
|||
FROM python:3
|
||||
# Python 3.9.6
|
||||
FROM python@sha256:736b76eb3f64778646ce0051fb5fed4dfbf67016e51563946230ca8bb40ac687
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ADD . /code
|
||||
WORKDIR /code
|
||||
|
|
91
Makefile
91
Makefile
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
|
||||
class InventoryUser(AbstractUser):
|
||||
pass
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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 %}
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"%}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{% include "core/partials/modal.html" with form=form modal_title="Add Device Category"%}
|
|
@ -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 %}
|
|
@ -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",
|
||||
|
|
|
@ -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')
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -11,6 +11,7 @@ services:
|
|||
|
||||
web:
|
||||
image: nebucatnetzer/network_inventory
|
||||
# build: .
|
||||
volumes:
|
||||
- .:/code
|
||||
environment:
|
||||
|
|
42
flake.lock
42
flake.lock
|
@ -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": {
|
||||
|
|
29
flake.nix
29
flake.nix
|
@ -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" ];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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': {
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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': {
|
||||
|
|
|
@ -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')),
|
||||
]
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
5
run.sh
|
@ -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
|
||||
|
|
|
@ -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)])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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
|
|
@ -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))
|
|
@ -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))
|
|
@ -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
|
|
@ -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'),
|
||||
|
|
|
@ -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})
|
||||
|
|
Loading…
Reference in New Issue