Skip to content

Commit d1865bc

Browse files
committed
ability to delete quality profiles (as long as they're not system-wide defaults)
1 parent e151826 commit d1865bc

File tree

7 files changed

+96
-7
lines changed

7 files changed

+96
-7
lines changed

src/frontend/src/app/api.service.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,23 @@ export class ApiService {
362362
);
363363
}
364364

365+
public deleteQualityProfile(id: number): Observable<any> {
366+
return this.http.delete(`${this.API_URL_QUALITY_PROFILES}${id}/`, {headers: this._requestHeaders()}).pipe(
367+
map((data: any) => {
368+
// remove this quality profile
369+
this.qualityProfiles = this.qualityProfiles.filter(profile => profile.id !== id);
370+
// unset quality profile from Movie/TVShow/TVSeasonRequest media records
371+
[this.watchMovies, this.watchTVShows, this.watchTVSeasonRequests].forEach((watchMediaList) => {
372+
watchMediaList.forEach((watchMedia) => {
373+
if (watchMedia.quality_profile === id) {
374+
watchMedia.quality_profile = null;
375+
}
376+
})
377+
})
378+
}),
379+
);
380+
}
381+
365382
public searchTorrents(query: string, mediaType: string): Observable<any> {
366383
return this.http.get(`${this.API_URL_SEARCH_TORRENTS}?q=${query}&media_type=${mediaType}`, {headers: this._requestHeaders()}).pipe(
367384
map((data: any) => {

src/frontend/src/app/settings/quality-profiles.component.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@ <h4 class="modal-title">Quality Profiles</h4>
4646
Require 5.1 Surround Sound
4747
</label>
4848
</div>
49-
<div *ngIf="{'valid': form.controls.profiles.controls[i].valid} as validity" class="text-end">
50-
<button type="button" class="btn" [ngClass]="{'btn-outline-success': validity.valid, 'btn-outline-danger': !validity.valid}" (click)="save(form.controls.profiles.controls[i])" [disabled]="!validity.valid">Save</button>
49+
<div class="my-2 text-end">
50+
<button type="button" class="btn btn-outline-danger me-2" (click)="delete(i)">Delete</button>
51+
<ng-container *ngIf="{'ok': form.controls.profiles.controls[i].valid} as validity">
52+
<button type="button" class="btn" [ngClass]="{'btn-success': validity.ok, 'btn-danger': !validity.ok}" (click)="save(form.controls.profiles.controls[i])" [disabled]="!validity.ok">Save</button>
53+
</ng-container>
5154
</div>
5255
</div>
5356
</div>

src/frontend/src/app/settings/quality-profiles.component.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,28 @@ export class QualityProfilesComponent implements OnInit {
5252
}
5353
})
5454
}
55+
56+
public delete(formArrayIndex: number) {
57+
const profileFormGroup = this.form.controls.profiles.controls[formArrayIndex];
58+
this.isLoading = true;
59+
const data = profileFormGroup.value;
60+
this.apiService.deleteQualityProfile(data.id).subscribe({
61+
next: () => {
62+
// remove form group
63+
this.form.controls.profiles.removeAt(formArrayIndex);
64+
this.toastr.success('Successfully deleted quality profile');
65+
this.isLoading = false;
66+
},
67+
error: (error) => {
68+
// display specific error message if it exists
69+
if (error?.error?.message) {
70+
this.toastr.error(error.error.message);
71+
} else {
72+
this.toastr.error('An unknown error occurred deleting the quality profile');
73+
}
74+
console.error(error);
75+
this.isLoading = false;
76+
}
77+
})
78+
}
5579
}

src/nefarious/api/permissions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
class IsAuthenticatedDjangoObjectUser(IsAuthenticated):
55
"""
6-
User must be authenticated and updating object they "own"
6+
User must be authenticated and updating an object they "own"
77
"""
88

99
def has_object_permission(self, request, view, obj):
1010

11-
# update operation, not staff, and the requesting user differs from the object's user
11+
# non-staff/non-safe operation and the requesting user differs from the object's user
1212
if request.method not in SAFE_METHODS:
1313
if not request.user.is_staff and getattr(obj, 'user', None) and request.user != obj.user:
1414
return False

src/nefarious/api/viewsets.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,25 @@ def get_queryset(self):
216216

217217
@method_decorator(gzip_page, name='dispatch')
218218
class QualityProfileViewSet(viewsets.ModelViewSet):
219+
permission_classes = (IsAdminUser,)
219220
queryset = QualityProfile.objects.all()
220221
serializer_class = QualityProfileSerializer
221222

223+
def destroy(self, request, *args, **kwargs):
224+
# prevent the deletion of the default tv/movies profiles in NefariousSettings
225+
nefarious_settings = NefariousSettings.get()
226+
if self.get_object() in [nefarious_settings.quality_profile_tv, nefarious_settings.quality_profile_movies]:
227+
media_type = ''
228+
if self.get_object() == nefarious_settings.quality_profile_tv:
229+
media_type = 'tv'
230+
elif self.get_object() == nefarious_settings.quality_profile_movies:
231+
media_type = 'movies'
232+
raise ValidationError({
233+
'success': False,
234+
'message': f"Cannot delete profile '{self.get_object()}' since it's used as a system-wide default for {media_type}",
235+
})
236+
return super().destroy(request, *args, **kwargs)
237+
222238

223239
class TorrentBlacklistViewSet(viewsets.ModelViewSet):
224240
queryset = TorrentBlacklist.objects.all()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Generated by Django 3.0.2 on 2024-08-15 21:59
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('nefarious', '0090_auto_20240812_2209'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='watchmovie',
16+
name='quality_profile',
17+
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='nefarious.QualityProfile'),
18+
),
19+
migrations.AlterField(
20+
model_name='watchtvseasonrequest',
21+
name='quality_profile',
22+
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='nefarious.QualityProfile'),
23+
),
24+
migrations.AlterField(
25+
model_name='watchtvshow',
26+
name='quality_profile',
27+
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='nefarious.QualityProfile'),
28+
),
29+
]

src/nefarious/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ class WatchMovie(WatchMediaBase):
153153
tmdb_movie_id = models.IntegerField(unique=True)
154154
name = models.CharField(max_length=255)
155155
poster_image_url = models.CharField(max_length=1000)
156-
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.CASCADE, null=True)
156+
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.SET_NULL, null=True)
157157

158158
class Meta:
159159
ordering = ('name',)
@@ -176,7 +176,7 @@ class WatchTVShow(models.Model):
176176
release_date = models.DateField(null=True, blank=True)
177177
auto_watch = models.BooleanField(default=False) # whether to automatically watch future seasons
178178
auto_watch_date_updated = models.DateField(null=True, blank=True) # date auto watch requested/updated
179-
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.CASCADE, null=True)
179+
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.SET_NULL, null=True)
180180

181181

182182
class Meta:
@@ -200,7 +200,7 @@ class WatchTVSeasonRequest(models.Model):
200200
user = models.ForeignKey(User, on_delete=models.CASCADE)
201201
watch_tv_show = models.ForeignKey(WatchTVShow, on_delete=models.CASCADE)
202202
season_number = models.IntegerField()
203-
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.CASCADE, null=True)
203+
quality_profile = models.ForeignKey(QualityProfile, on_delete=models.SET_NULL, null=True)
204204
collected = models.BooleanField(default=False)
205205
date_added = models.DateTimeField(auto_now_add=True)
206206
date_updated = models.DateTimeField(auto_now=True)

0 commit comments

Comments
 (0)