Skip to content

Commit 80306fd

Browse files
committed
Pre-review ready
1 parent 35cf0a1 commit 80306fd

File tree

4 files changed

+185
-49
lines changed

4 files changed

+185
-49
lines changed

warehouse/forklift/legacy.py

Lines changed: 106 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,34 @@ def _is_duplicate_file(db_session, filename, hashes):
759759
return None
760760

761761

762+
def _get_release_classifiers(db_session, classifiers_data):
763+
"""
764+
Go over the classifiers of a release, and add any missing ones
765+
to the database.
766+
"""
767+
768+
# Look up all of the valid classifiers
769+
all_classifiers = db_session.query(Classifier).all()
770+
771+
# Get all the classifiers for this release
772+
release_classifiers = [
773+
c for c in all_classifiers if c.classifier in classifiers_data
774+
]
775+
776+
# Determine if we need to add any new classifiers to the database
777+
missing_classifiers = set(classifiers_data or []) - set(
778+
c.classifier for c in release_classifiers
779+
)
780+
781+
# Add any new classifiers to the database
782+
if missing_classifiers:
783+
for missing_classifier_name in missing_classifiers:
784+
missing_classifier = Classifier(classifier=missing_classifier_name)
785+
db_session.add(missing_classifier)
786+
release_classifiers.append(missing_classifier)
787+
return release_classifiers
788+
789+
762790
@view_config(
763791
route_name="forklift.legacy.file_upload",
764792
uses_session=True,
@@ -1039,6 +1067,27 @@ def file_upload(request):
10391067
) from None
10401068

10411069
canonical_version = packaging.utils.canonicalize_version(form.version.data)
1070+
1071+
form_metadata_fields = {
1072+
# This is a list of all the fields in the form that we
1073+
# should pull off and insert into our new release.
1074+
"version",
1075+
"summary",
1076+
"license",
1077+
"author",
1078+
"author_email",
1079+
"maintainer",
1080+
"maintainer_email",
1081+
"keywords",
1082+
"platform",
1083+
"home_page",
1084+
"download_url",
1085+
"requires_python",
1086+
}
1087+
1088+
# Determine if this is a draft release or a published one
1089+
release_is_draft = bool(request.headers.get("Is-Draft", default=False))
1090+
10421091
try:
10431092
release = (
10441093
request.db.query(Release)
@@ -1060,30 +1109,10 @@ def file_upload(request):
10601109
.one()
10611110
)
10621111
except NoResultFound:
1063-
1064-
# Determine if this is a draft release or a published one
1065-
release_is_draft = bool(request.headers.get("Is-Draft", default=False))
1066-
1067-
# Look up all of the valid classifiers
1068-
all_classifiers = request.db.query(Classifier).all()
1069-
1070-
# Get all the classifiers for this release
1071-
release_classifiers = [
1072-
c for c in all_classifiers if c.classifier in form.classifiers.data
1073-
]
1074-
1075-
# Determine if we need to add any new classifiers to the database
1076-
missing_classifiers = set(form.classifiers.data or []) - set(
1077-
c.classifier for c in release_classifiers
1112+
release_classifiers = _get_release_classifiers(
1113+
request.db, form.classifiers.data
10781114
)
10791115

1080-
# Add any new classifiers to the database
1081-
if missing_classifiers:
1082-
for missing_classifier_name in missing_classifiers:
1083-
missing_classifier = Classifier(classifier=missing_classifier_name)
1084-
request.db.add(missing_classifier)
1085-
release_classifiers.append(missing_classifier)
1086-
10871116
release = Release(
10881117
project=project,
10891118
project_name=project.name,
@@ -1110,25 +1139,7 @@ def file_upload(request):
11101139
html=rendered or "",
11111140
rendered_by=readme.renderer_version(),
11121141
),
1113-
**{
1114-
k: getattr(form, k).data
1115-
for k in {
1116-
# This is a list of all the fields in the form that we
1117-
# should pull off and insert into our new release.
1118-
"version",
1119-
"summary",
1120-
"license",
1121-
"author",
1122-
"author_email",
1123-
"maintainer",
1124-
"maintainer_email",
1125-
"keywords",
1126-
"platform",
1127-
"home_page",
1128-
"download_url",
1129-
"requires_python",
1130-
}
1131-
},
1142+
**{k: getattr(form, k).data for k in form_metadata_fields},
11321143
uploader=request.user,
11331144
uploaded_via=request.user_agent,
11341145
published=None if release_is_draft else datetime.now(tz=timezone.utc),
@@ -1146,17 +1157,48 @@ def file_upload(request):
11461157
submitted_from=request.remote_addr,
11471158
)
11481159
)
1149-
11501160
project.record_event(
11511161
tag="project:release:add",
11521162
ip_address=request.remote_addr,
11531163
additional={
11541164
"submitted_by": request.user.username,
11551165
"canonical_version": release.canonical_version,
1156-
"version_or_draft": release.version_or_draft,
11571166
},
11581167
)
1159-
1168+
else:
1169+
# An existing release was found. Update its metadata if it's a draft.
1170+
if release.is_draft:
1171+
release_classifiers = _get_release_classifiers(
1172+
request.db, form.classifiers.data
1173+
)
1174+
for rc in release_classifiers:
1175+
release._classifiers = release_classifiers
1176+
new_dependencies = list(
1177+
_construct_dependencies(
1178+
form,
1179+
{
1180+
"requires": DependencyKind.requires,
1181+
"provides": DependencyKind.provides,
1182+
"obsoletes": DependencyKind.obsoletes,
1183+
"requires_dist": DependencyKind.requires_dist,
1184+
"provides_dist": DependencyKind.provides_dist,
1185+
"obsoletes_dist": DependencyKind.obsoletes_dist,
1186+
"requires_external": DependencyKind.requires_external,
1187+
"project_urls": DependencyKind.project_url,
1188+
},
1189+
)
1190+
)
1191+
if new_dependencies:
1192+
release.dependencies = new_dependencies
1193+
if form.description.data:
1194+
release.description.content_type = description_content_type
1195+
release.description.raw = form.description.data or ""
1196+
release.description.html = rendered or ""
1197+
release.description.rendered_by = readme.renderer_version()
1198+
for field in form_metadata_fields:
1199+
field_value = getattr(form, field).data
1200+
if field_value:
1201+
setattr(release, field, field_value)
11601202
# TODO: We need a better solution to this than to just do it inline inside
11611203
# this method. Ideally the version field would just be sortable, but
11621204
# at least this should be some sort of hook or trigger.
@@ -1274,8 +1316,15 @@ def file_upload(request):
12741316
"The digest supplied does not match a digest calculated "
12751317
"from the uploaded file.",
12761318
)
1277-
# Skip duplicate check for files when it's a draft release
1278-
if not release.is_draft:
1319+
# Skip duplicate check for files when it's a draft release,
1320+
# and delete existing files instead
1321+
if release.is_draft:
1322+
existing_file = (
1323+
request.db.query(File).filter(File.filename == filename).first()
1324+
)
1325+
if existing_file is not None:
1326+
request.db.delete(existing_file)
1327+
else:
12791328
# Check to see if the file that was uploaded exists already or not.
12801329
is_duplicate = _is_duplicate_file(request.db, filename, file_hashes)
12811330
if is_duplicate:
@@ -1363,7 +1412,17 @@ def file_upload(request):
13631412
# TODO: This should be handled by some sort of database trigger or a
13641413
# SQLAlchemy hook or the like instead of doing it inline in this
13651414
# view.
1366-
request.db.add(Filename(filename=filename))
1415+
#
1416+
# If this is a draft release and the filename is
1417+
# already on the registry, do nothing.
1418+
if release.is_draft:
1419+
if (
1420+
request.db.query(Filename).filter(Filename.filename == filename).first()
1421+
is None
1422+
):
1423+
request.db.add(Filename(filename=filename))
1424+
else:
1425+
request.db.add(Filename(filename=filename))
13671426

13681427
# Store the information about the file in the database.
13691428
file_ = File(

warehouse/manage/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1534,7 +1534,7 @@ def _error(message):
15341534
self.request.route_path(
15351535
"manage.project.release",
15361536
project_name=self.release.project.name,
1537-
version=self.release.version,
1537+
version=self.release.version_or_draft,
15381538
)
15391539
)
15401540

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
"""
13+
Draft Releases
14+
15+
Revision ID: 93388f06f5e7
16+
Revises: 80018e46c5a4
17+
Create Date: 2020-09-29 18:23:35.798571
18+
"""
19+
20+
from alembic import op
21+
import sqlalchemy as sa
22+
23+
24+
revision = "93388f06f5e7"
25+
down_revision = "80018e46c5a4"
26+
27+
# Note: It is VERY important to ensure that a migration does not lock for a
28+
# long period of time and to ensure that each individual migration does
29+
# not break compatibility with the *previous* version of the code base.
30+
# This is because the migrations will be ran automatically as part of the
31+
# deployment process, but while the previous version of the code is still
32+
# up and running. Thus backwards incompatible changes must be broken up
33+
# over multiple migrations inside of multiple pull requests in order to
34+
# phase them in over multiple deploys.
35+
36+
37+
def upgrade():
38+
# ### commands auto generated by Alembic - please adjust! ###
39+
op.add_column("releases", sa.Column("project_name", sa.Text(), nullable=True))
40+
op.add_column("releases", sa.Column("published", sa.DateTime(), nullable=True))
41+
# ### end Alembic commands ###
42+
# Fill the project_name and published columns
43+
op.execute(
44+
"""
45+
UPDATE releases AS target SET published = (
46+
SELECT created
47+
FROM releases AS source WHERE source.id = target.id
48+
);
49+
"""
50+
)
51+
op.execute(
52+
"""
53+
UPDATE releases AS r SET project_name = (
54+
SELECT name
55+
FROM projects AS p WHERE p.id = r.project_id
56+
)
57+
"""
58+
)
59+
# Create the hashing database function for our draft identifiers
60+
op.execute(
61+
"""
62+
CREATE FUNCTION make_draft_hash(project_name text, version text) returns text as $$
63+
SELECT md5(project_name || version)
64+
$$
65+
LANGUAGE SQL
66+
IMMUTABLE
67+
RETURNS NULL ON NULL INPUT;
68+
"""
69+
)
70+
71+
72+
def downgrade():
73+
# ### commands auto generated by Alembic - please adjust! ###
74+
op.drop_column("releases", "published")
75+
op.drop_column("releases", "project_name")
76+
# ### end Alembic commands ###
77+
op.execute("DROP FUNCTION make_draft_hash")

warehouse/templates/manage/history.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ <h2>{% trans %}Security history{% endtrans %}</h2>
3131
{% trans %}Created by:{% endtrans %} <a href="{{ request.route_path('accounts.profile', username=event.additional.created_by) }}">{{ event.additional.created_by }}</a>
3232
</small>
3333
{% elif event.tag == "project:release:add" %}
34-
<strong>{% trans href=request.route_path('manage.project.release', project_name=project.name, version=event.additional.version_or_draft), version=event.additional.canonical_version %}<a href="{{ href }}">Release version {{ version }}</a> created{% endtrans %}</strong><br>
34+
<strong>{% trans href=request.route_path('manage.project.release', project_name=project.name, version=event.additional.canonical_version), version=event.additional.canonical_version %}<a href="{{ href }}">Release version {{ version }}</a> created{% endtrans %}</strong><br>
3535
<small>
3636
{% trans %}Added by:{% endtrans %} <a href="{{ request.route_path('accounts.profile', username=event.additional.submitted_by) }}">{{ event.additional.submitted_by }}</a>
3737
</small>

0 commit comments

Comments
 (0)